Tracking issue for request Routing
Tonic should allow a single socket/server to serve multiple services. Routing is the way to get there.
Current progress: https://github.com/hyperium/tonic/blob/master/tonic-interop/src/bin/server.rs#L73
@rlabrecque thanks for opening this!
We've had some ideas there are two that stand out to me currently:
1) Use a macro to generate the code that looks similar to the one in the interop server.
Server::builder()
.serve(addr, route! {
"test_service" => TestService::default(),
"unimplemented_service" => UnimplementedService::default()
})
.await?;
2)
let routed_svc = tonic::Router::new(TestService::default())
.with(UnimplementedService::default())
Server::builder()
.serve(addr, routed_svc)
.await?;
This one is more of an idea, and I'm not sure how to accomplish it without a ton of boxing 馃槩
Just wanted to throw in my use case for this:
We have a pretty decent REST setup in go, and one thing that I'm missing with tower-grpc is routing. grpc-router above is working alright for my needs now, but I don't think it's going to scale to our REST setup.
The biggest part missing would be the ability to add routes later, and then get a list of the routes.
Here's what I'd want to write:
let router_service = Router::new(
GreeterService::new(),
PingService::new(),
SomeOtherService::new(),
);
// Add the APIListService and HealthCheckService after we created the router_service, and pass
// it the router_service so that we can get a list of all of the services.
let api_list_service = APIListService::new(&router_service);
router_service.add(api_list_service);
let healthcheck_service = HealthCheckService::new(&router_service);
router_service.add(healthcheck_service);
let mut server = Server::new(router_service);
// Bind server to http2 server and continue as normally
// ...
#[derive(Clone, Debug)]
pub struct APIListService {
services: Vec<std::string::String>
}
impl APIListService {
pub fn new(router: &Router) {
server::APIListServer::new(APIListService { services: router.get_routes() } )
}
}
Copied from: https://github.com/tower-rs/tower-grpc/issues/2#issuecomment-536231778
@rlabrecque what is your use case around needing to add services at a later point?
As I understand it, so that the api_list_service can:
healthcheck_service in api list reqsAlthough this could be done without involving the router like the following (although it's not super ergonomic):
let greeter = GreeterService::new();
let ping = PingService::new();
let other = SomeOtherService::new();
let api_list = ApiListService::new()
.with::<GreeterService>()
.with::<PingService>()
.with::<SomeOtherService>();
let healthcheck = HealthCheckService::new()
.with::<GreeterService>()
.with::<PingService>()
.with::<SomeOtherService>()
.with::<ApiListService>();
// boxed version
let router = Router::new()
.with(greeter)
.with(ping)
.with(other)
.with(api_list)
.with(healthcheck);
// static version
let router = router! {greeter, ping, other, api_list, healthcheck};
let mut server = Server::new(router);
// Bind server to http2 server and continue as normally
// ...
So long as every service could expose its own routes. I've written a quick PoC that allows that via a trait and associated consts, something like:
pub mod router {
// every MakeService gets a generated impl for:
pub trait Routed {
const PREFIX: &'static str;
const ROUTES: &'static [&'static str];
}
}
@nytopop That makes sense that was what I was kinda thinking. We can totally add that trait and implement it for codegen. Each async trait impl can include that.