hyperium / tonic

A native gRPC client & server implementation with async/await support.
https://docs.rs/tonic
MIT License
9.69k stars 990 forks source link

NamedService makes it impossible to wrap individual services with non-intercept layers #1875

Open DoumanAsh opened 3 weeks ago

DoumanAsh commented 3 weeks ago

Current state of affairs when it comes to building complex tower services makes it impossible to use generic tower-http layers when attempting to wrap individual grpc service with layer via ServiceBuilder due to NamedService requirement

Do you think it would be useful to add some sort of wrapper type that would allow to access individual NamedService from within Layered service?

zakhenry commented 2 weeks ago

See https://github.com/hyperium/tonic/pull/1893 you are able to work around it by calling into_router on the tonic route builder. If you have another use case that would make #1893 make more sense then let me know and I'd be happy to reopen it.

lcmgh commented 5 days ago

Sharing my secret sauce

//! Utilities for tonic.

use tonic::server::NamedService;
use tower::{Layer, Service, ServiceBuilder};

/// A helper function to create a layered service as in tonic one normally must
/// apply a layer to all services.
///
/// Tonic expects each service to implement the `NamedService` trait. Once a service is layered
/// the trait is not implemented anymore.
///
/// This function will automatically
/// implement the `NamedService` trait for the layered service.
pub fn layered_service<L, S>(service: S, layer: L) -> LayeredService<<L as Layer<S>>::Service, S>
where
    S: NamedService,
    L: Layer<S>,
{
    let service = ServiceBuilder::new().layer(layer).service(service);

    LayeredService {
        inner: service,
        _marker: std::marker::PhantomData::<S>,
    }
}

impl<S, I> NamedService for LayeredService<S, I>
where
    I: NamedService,
{
    const NAME: &'static str = I::NAME;
}

/// A service that is wrapped in a layer.
///
/// In tonic one can only add a layer that is applied to all services. This struct allows to add a layer to a single service.
#[derive(Clone, Debug)]
pub struct LayeredService<S, I> {
    inner: S,
    _marker: std::marker::PhantomData<I>,
}

impl<B, S, I> Service<hyper::Request<B>> for LayeredService<S, I>
where
    S: Service<hyper::Request<B>> + Clone,
{
    type Response = S::Response;
    type Error = S::Error;
    type Future = S::Future;

    fn poll_ready(
        &mut self,
        cx: &mut std::task::Context<'_>,
    ) -> std::task::Poll<Result<(), Self::Error>> {
        self.inner.poll_ready(cx)
    }

    fn call(&mut self, req: hyper::Request<B>) -> Self::Future {
        self.inner.call(req)
    }
}