Juniper rust: sharing request data with the resolvers with the FromRequest trait - rust

can anyone help get my head around the FromRequest trait.
As I understand, we can use it to share data with the resolvers.
I'm trying to inject the http headers so I can authenticate users before some actions in the resolvers.
See my code below.
Looks like the from_request isn't even called.
I'm using rocket 0.5.rc1
Thank you for your help
use juniper::{graphql_object, EmptySubscription, RootNode};
use mongodb::Client;
use mongodb::Database;
use rocket::{catch, catchers};
use rocket::{response::content, Rocket, State};
type Schema = RootNode<'static, Query, Mutations, EmptySubscription<AppContext>>;
use rocket::{
http::Status,
request::{FromRequest, Outcome},
Request,
};
struct Mutations;
struct Query;
#[derive(Debug)]
pub struct AppContext {
pub mongodb_pool: Database,
pub user: String,
}
impl juniper::Context for AppContext {}
#[rocket::async_trait]
impl<'r> FromRequest<'r> for AppContext {
type Error = ();
async fn from_request(request: &'r Request<'_>) -> Outcome<AppContext, Self::Error> {
let client = Client::with_uri_str("mongodb://admin:admin#localhost:28017").await;
let db = client.unwrap().database("override");
Outcome::Success(AppContext { mongodb_pool: db, user: "from request".to_string() }, )
}
}
#[graphql_object(context = AppContext)]
impl Query {
fn api_version(db: &AppContext) -> &str {
println!("{:?}", db.user);
"1.0"
}
}
#[graphql_object(context = AppContext)]
impl Mutations {
fn api_version(db: &AppContext) -> String {
"1.0".to_string()
}
}
#[rocket::get("/")]
fn graphiql() -> content::Html<String> {
juniper_rocket::graphiql_source("/graphql", None)
}
#[rocket::get("/graphql?<request>")]
async fn get_graphql_handler(
context: &State<AppContext>,
request: juniper_rocket::GraphQLRequest,
schema: &State<Schema>,
) -> juniper_rocket::GraphQLResponse {
request.execute(&*schema, &*context).await
}
#[rocket::post("/graphql", data = "<request>")]
async fn post_graphql_handler(
context: &State<AppContext>,
request: juniper_rocket::GraphQLRequest,
schema: &State<Schema>,
) -> juniper_rocket::GraphQLResponse {
request.execute(&*schema, &*context).await
}
#[tokio::main]
async fn main() {
let client = Client::with_uri_str("mongodb://admin:admin#localhost:28017").await;
let db = client.unwrap().database("app_db");
Rocket::build()
.manage(AppContext{ mongodb_pool: db, user: "from build".to_string() })
.manage(Schema::new(
Query,
Mutations,
EmptySubscription::<AppContext>::new(),
))
.mount(
"/",
rocket::routes![graphiql, get_graphql_handler, post_graphql_handler],
)
.launch()
.await
.expect("server failed to launch");
}

Related

Actix and Inventory crate

I'm trying to make it possible to register an actix route automatically. To do so I found the crate Inventory which seems to answer my need.
I have the following code:
#[get("/hello2/{name}")]
async fn greet2(req: HttpRequest) -> String {
let name = req.match_info().get("name").unwrap_or("World");
let a = format!("Hello2 {}!", &name);
a
}
pub struct TestStruct {
test: fn(HttpRequest) -> dyn Future<Output = String>
}
inventory::collect!(TestStruct);
inventory::submit! {
let mut a = TestStruct {
test: <greet2 as HttpServiceFactory>::register
};
a.test.push(greet2::register::greet2);
a
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(move || {
let mut app = App::new();
for route in inventory::iter::<AutomaticHttpService> {
app = app.service(route);
}
app
})
.bind(("127.0.0.1", 8080))?
.workers(2)
.run()
.await
}
My code does not compile as I cannot access the requested function pointer and I cannot use the HttpServiceFactory trait as it is not declared in the same file. I also tried to use the std::any::Any type and then cast to HttpServiceFactory (maybe using unsafe) but without success because its size was not known at compile time.
Would you have any clues to unblock my situation?

Axum: pass parameters to handlers

new to rust here. I am trying to build a simple server that provides and decodes JWT tokens and I am missing a big part. Here is the code:
pub struct Server {
pub host: String,
pub port: String,
pub public_key: String,
pub private_key: String
}
impl Server {
pub async fn start(&self) {
let routes = Router::new()
.route("/", get(check))
.route("/auth", post(auth));
let mut hostport = String::from(&self.host);
hostport.push_str(":");
hostport.push_str(&self.port);
println!("{}", hostport);
let addr : SocketAddr = hostport.parse().expect("invalid host:port pair");
axum::Server::bind(
&addr
).serve(routes.into_make_service()).await.unwrap();
}
}
async fn auth(Json(payload): Json<LoginInput>) -> impl IntoResponse {
let claims = Claims::create(Duration::from_hours(1));
RS384PublicKey::from_pem("id_like_to_put_Server::public_key_here").sign(claims)?;
let lo = LoginOutput{
token: payload.username
};
(StatusCode::OK, Json(lo))
}
As you can see Server holds routing logic and applies configuration. Among configuration there is a public key I'd like to use in order to sign the JWT token (I am using jwt_simple to achieve that). Since public key is a Server's attribute, I want to pass that value to the auth handler but I can't figure out how to do that. How can I pass a parameter to an Axum handler and sign the token is generated inside?
Although here are the examples using both Extension and State, State is preferred as from the docs of Axum:
You should prefer using State if possible since it’s more type safe. The downside is that its less dynamic than request extensions.
In Cargo.toml:
[dependencies]
axum = "0.6.0-rc.2"
serde = { version = "1.0.147", features = ["derive"] }
tokio = { version = "1.21.2", features = ["macros", "rt-multi-thread"] }
Using State:
use std::net::SocketAddr;
use axum::{
extract::State,
response::IntoResponse,
routing::{get, post},
Json, Router,
};
use serde::Deserialize;
#[derive(Clone)]
pub struct ServerConfig {
pub host: String,
pub port: String,
pub public_key: String,
pub private_key: String,
}
#[derive(Deserialize)]
pub struct LoginInput {
username: String,
password: String,
}
#[tokio::main]
async fn main() {
let server_config = ServerConfig {
host: "0.0.0.0".into(),
port: "8080".into(),
public_key: "public_key".into(),
private_key: "private_key".into(),
};
let addr: SocketAddr = format!("{}:{}", server_config.host, server_config.port)
.parse()
.unwrap();
let routes = Router::with_state(server_config) // state will be available to all the routes
.route("/", get(check))
.route("/auth", post(auth));
axum::Server::bind(&addr)
.serve(routes.into_make_service())
.await
.unwrap();
}
async fn check() -> &'static str {
"check"
}
async fn auth(
State(server_config): State<ServerConfig>, // extract state in this handler
// `Json` supports any type that implements `serde::Deserialize`
Json(payload): Json<LoginInput>,
) -> impl IntoResponse {
// use server_config and payload to run the `auth` logic
println!("host: {}", server_config.host);
"jwt"
}
Using Extension
use std::net::SocketAddr;
use axum::{
response::IntoResponse,
routing::{get, post},
Extension, Json, Router,
};
use serde::Deserialize;
#[derive(Clone)]
pub struct ServerConfig {
pub host: String,
pub port: String,
pub public_key: String,
pub private_key: String,
}
#[derive(Deserialize)]
pub struct LoginInput {
username: String,
password: String,
}
#[tokio::main]
async fn main() {
let server_config = ServerConfig {
host: "0.0.0.0".into(),
port: "8080".into(),
public_key: "public_key".into(),
private_key: "private_key".into(),
};
let addr: SocketAddr = format!("{}:{}", server_config.host, server_config.port)
.parse()
.unwrap();
let routes = Router::new()
.route("/", get(check))
.route("/auth", post(auth))
.layer(Extension(server_config));
axum::Server::bind(&addr)
.serve(routes.into_make_service())
.await
.unwrap();
}
async fn check() -> &'static str {
"check"
}
async fn auth(
Extension(server_config): Extension<ServerConfig>,
// `Json` supports any type that implements `serde::Deserialize`
Json(payload): Json<LoginInput>,
) -> impl IntoResponse {
// use server_config and payload to run the `auth` logic
println!("host: {}", server_config.host);
"jwt"
}

Rust Actix Websockets & Actix Broker

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?

Actix web - sqlx - juniper: I got 'Tried to resolve async field users on type Some("Query") with a sync resolver'

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" ] }

Actix SyncArbiter registry

I'm trying to implement a pool of 10 Redis of conections using a SyncArbiter for different actors to use. Say that we have an actor named Bob that has to use a Redis actor to accomplish it's task.
While this is achievable in the following manner:
// crate, use and mod statements have been omitted to lessen clutter
/// FILE main.rs
pub struct AppState {
pub redis: Addr<Redis>,
pub bob: Addr<Bob>
}
fn main() {
let system = actix::System::new("theatre");
server::new(move || {
let redis_addr = SyncArbiter::start(10, || Redis::new("redis://127.0.0.1").unwrap());
let bob_addr = SyncArbiter::start(10, || Bob::new());
let state = AppState {
redis: redis_addr,
bob: bob_addr
};
App::with_state(state).resource("/bob/eat", |r| {
r.method(http::Method::POST)
.with_async(controllers::bob::eat)
})
})
.bind("0.0.0.0:8080")
.unwrap()
.start();
println!("Server started.");
system.run();
}
/// FILE controllers/bob.rs
pub struct Food {
name: String,
kcal: u64
}
pub fn eat(
(req, state): (Json<Food>, State<AppState>),
) -> impl Future<Item = HttpResponse, Error = Error> {
state
.bob
.send(Eat::new(req.into_inner()))
.from_err()
.and_then(|res| match res {
Ok(val) => {
println!("==== BODY ==== {:?}", val);
Ok(HttpResponse::Ok().into())
}
Err(_) => Ok(HttpResponse::InternalServerError().into()),
})
}
/// FILE actors/redis.rs
#[derive(Debug)]
pub struct Redis {
pub client: Client
}
pub struct RunCommand(Cmd);
impl RunCommand {
pub fn new(cmd: Cmd) -> Self {
RunCommand(cmd)
}
}
impl Message for RunCommand {
type Result = Result<RedisResult<String>, ()>;
}
impl Actor for Redis {
type Context = SyncContext<Self>;
}
impl Handler<RunCommand> for Redis {
type Result = Result<RedisResult<String>, ()>;
fn handle(&mut self, msg: RunCommand, _context: &mut Self::Context) -> Self::Result {
println!("Redis received command!");
Ok(Ok("OK".to_string()))
}
}
impl Redis {
pub fn new(url: &str) -> Result<Self, RedisError> {
let client = match Client::open(url) {
Ok(client) => client,
Err(error) => return Err(error)
};
let redis = Redis {
client: client,
};
Ok(redis)
}
}
/// FILE actors/bob.rs
pub struct Bob;
pub struct Eat(Food);
impl Message for Eat {
type Result = Result<Bob, ()>;
}
impl Actor for Eat {
type Context = SyncContext<Self>;
}
impl Handler<Eat> for Bob {
type Result = Result<(), ()>;
fn handle(&mut self, msg: Eat, _context: &mut Self::Context) -> Self::Result {
println!("Bob received {:?}", &msg);
// How to get a Redis actor and pass data to it here?
Ok(msg.datapoint)
}
}
impl Bob {
pub fn new() -> () {
Bob {}
}
}
From the above handle implementation in Bob, it's unclear how Bob could get the address of an Redis actor. Or send any message to any Actor running in a SyncArbiter.
The same could be achieved using a regular Arbiter and a Registry but, as far as I am aware of, Actix doesn't allow multiple same actors (e.g. we can't start 10 Redis actors using a regular Arbiter).
To formalize my questions:
Is there a Registry for SyncArbiter actors
Can I start multiple same type actors in a regular Arbiter
Is there a better / more canonical way to implement a connection pool
EDIT
Versions:
actix 0.7.9
actix_web 0.7.19
futures = "0.1.26"
rust 1.33.0
I found the answer myself.
Out-of-the box there is no way for an Actor with a SyncContext to be retrieved from the registry.
Given my above example. For the actor Bob to send any kind of message to the Redis actor it needs to know the address of the Redis actor. Bob can get the address of Redis explicitly - contained in a message sent to it or read from some kind of shared state.
I wanted a system similar to Erlang's so I decided against passing the addresses of actors through messages as it seemed too laborious, error prone and in my mind it defeats the purpose of having an actor based concurrency model (since no one actor can message any other actor).
Therefore I investigated the idea of a shared state, and decided to implement my own SyncRegistry that would be an analog to the Actix standard Registry - whic does exactly what I want but not for Actors with a SyncContext.
Here is the naive solution i coded up: https://gist.github.com/monorkin/c463f34764ab23af2fd0fb0c19716177
With the following setup:
fn main() {
let system = actix::System::new("theatre");
let addr = SyncArbiter::start(10, || Redis::new("redis://redis").unwrap());
SyncRegistry::set(addr);
let addr = SyncArbiter::start(10, || Bob::new());
SyncRegistry::set(addr);
server::new(move || {
let state = AppState {};
App::with_state(state).resource("/foo", |r| {
r.method(http::Method::POST)
.with_async(controllers::foo::create)
})
})
.bind("0.0.0.0:8080")
.unwrap()
.start();
println!("Server started.");
system.run();
}
The actor Bob can get the address of Redis in the following manner, from any point in the program:
impl Handler<Eat> for Bob {
type Result = Result<(), ()>;
fn handle(&mut self, msg: Eat, _context: &mut Self::Context) -> Self::Result {
let redis = match SyncRegistry::<Redis>::get() {
Some(redis) => redis,
_ => return Err(())
};
let cmd = redis::cmd("XADD")
.arg("things_to_eat")
.arg("*")
.arg("data")
.arg(&msg.0)
.to_owned();
redis.clone().lock().unwrap().send(RunCommand::new(cmd)).wait().unwrap();
}
}

Resources