Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
DoumanAsh opened this issue Aug 20, 2024 · 2 comments

Comments

@DoumanAsh
Copy link
Contributor

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
Copy link
Contributor

zakhenry commented Aug 29, 2024

See #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
Copy link

lcmgh commented Sep 9, 2024

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)
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants