I am trying to see if this is the right tool for my use case and playing with some hello worlds.
I currently have two proto definitions:
helloworld.proto
, implementing the service Greeter
(with associated messages, the same as in the hellowworld example)wonderfulworld.proto
, implementing the service Wonderful
(with associated messages, very similar, but does not take any argument in the request and returns something else).I followed the Hello World example of Tonic, and created my service with
Server::builder()
.add_service(GreeterServer::new(greeter))
.add_service(WonderfulServer::new(wonderful))
.serve(addr)
.await?;
Now the thing is that I would like to have a common variable, let's say world_name
, for both services Greeter and Wonderful to use and modify.
Could I do this, and what would be the recommended way to proceed?
I have tried:
Implementing both Wonderful
and Greeter
on the same MyGreeter
struct, but then I can't chain the add_service
as I did here, since the new()
constructor uses value and not borrow, and I don't know how else to do.
Creating a MyServer
struct with a world_name
variable, and look on the API/online if I could somehow use this struct as the server to add services, but I didn't find anything at all.
Using some form of global singleton... I guess it works but meh... not a big fan if it's possible to do otherwise
Following is my full server.rs
file in case it's useful.
use tonic::{transport::Server, Request, Response, Status};
use hello_wonderful_world::greeter_server::{Greeter, GreeterServer};
use hello_wonderful_world::{HelloReply, HelloRequest};
use hello_wonderful_world::wonderful_server::{Wonderful, WonderfulServer};
use hello_wonderful_world::{WonderfulRequest, WonderfulReply};
pub mod hello_wonderful_world {
tonic::include_proto!("helloworld");
tonic::include_proto!("wonderfulworld");
}
#[derive(Debug, Default)]
pub struct MyGreeter {}
#[derive(Debug, Default)]
pub struct MyWonderfulWorld{}
#[tonic::async_trait]
impl Greeter for MyGreeter {
async fn say_hello(
&self,
request: Request<HelloRequest>, // Accept request of type HelloRequest
) -> Result<Response<HelloReply>, Status> { // Return an instance of type HelloReply
println!("Got a request: {:?}", request);
let reply = hello_wonderful_world::HelloReply {
message: format!("Hello {}!", request.into_inner().name).into(), // We must use .into_inner() as the fields of gRPC requests and responses are private
};
Ok(Response::new(reply)) // Send back our formatted greeting
}
}
#[tonic::async_trait]
impl Wonderful for MyWonderfulWorld {
async fn its_a_wonderful_world(
&self,
request: Request<WonderfulRequest>)
-> Result<Response<WonderfulReply>, Status> {
let reply = hello_wonderful_world::WonderfulReply {
message: format!("And I said to {}: What a Wonderful World", request.into_inner().name),
};
Ok(Response::new(reply))
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let addr = "[::1]:50051".parse()?;
let greeter = MyGreeter::default();
let my_wonderful = MyWonderfulWorld::default();
Server::builder()
.add_service(GreeterServer::new(greeter))
.add_service(WonderfulServer::new(my_wonderful))
.serve(addr)
.await?;
Ok(())
}
You can try something like this
pub struct SharedState {
name: String,
age: u8,
}
#[derive(Debug, Default)]
pub struct MyGreeter {
shared_state: std::sync::Arc<std::sync::Mutex<SharedState>>,
}
#[derive(Debug, Default)]
pub struct MyWonderfulWorld {
shared_state: std::sync::Arc<std::sync::Mutex<SharedState>>,
}
use tonic::{transport::Server, Request, Response, Status};
use hello_wonderful_world::greeter_server::{Greeter, GreeterServer};
use hello_wonderful_world::{HelloReply, HelloRequest};
use hello_wonderful_world::wonderful_server::{Wonderful, WonderfulServer};
use hello_wonderful_world::{WonderfulRequest, WonderfulReply};
pub mod hello_wonderful_world {
tonic::include_proto!("helloworld");
tonic::include_proto!("wonderfulworld");
}
#[tonic::async_trait]
impl Greeter for MyGreeter {
async fn say_hello(
&self,
request: Request<HelloRequest>, // Accept request of type HelloRequest
) -> Result<Response<HelloReply>, Status> { // Return an instance of type HelloReply
println!("Got a request: {:?}", request);
// here get the shared state and lock it
if let Ok(shared_state) = self.shared_state.lock()
{
println!("Got the lock");
println!("Name: {}", shared_state.name);
println!("Age: {}", shared_state.age);
}
let reply = hello_wonderful_world::HelloReply {
message: format!("Hello {}!", request.into_inner().name).into(), // We must use .into_inner() as the fields of gRPC requests and responses are private
};
Ok(Response::new(reply)) // Send back our formatted greeting
}
}
#[tonic::async_trait]
impl Wonderful for MyWonderfulWorld {
async fn its_a_wonderful_world(
&self,
request: Request<WonderfulRequest>)
-> Result<Response<WonderfulReply>, Status> {
// here get the shared state and lock it
if let Ok(shared_state) = self.shared_state.lock()
{
println!("Got the lock");
println!("Name: {}", shared_state.name);
println!("Age: {}", shared_state.age);
}
let reply = hello_wonderful_world::WonderfulReply {
message: format!("And I said to {}: What a Wonderful World", request.into_inner().name),
};
Ok(Response::new(reply))
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let addr = "[::1]:50051".parse()?;
let shared_state = std::sync::Arc::new(std::sync::Mutex::new(SharedState {
name: "John".to_string(),
age: 32,
}));
let greeter = MyGreeter {
shared_state: shared_state.clone(),
};
let my_wonderful = MyWonderfulWorld {
shared_state: shared_state.clone(),
};
Server::builder()
.add_service(GreeterServer::new(greeter))
.add_service(WonderfulServer::new(my_wonderful))
.serve(addr)
.await?;
Ok(())
}
I used std::sync::Mutex
but you maybe can use std::sync::RwLock
.