Skip to content

Commit

Permalink
feat: Add Layered service to propagate NamedService implementation (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
tottoto authored Oct 3, 2024
1 parent 4c8cf36 commit 9b74abf
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 4 deletions.
2 changes: 1 addition & 1 deletion examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ routeguide = ["dep:async-stream", "tokio-stream", "dep:rand", "dep:serde", "dep:
reflection = ["dep:tonic-reflection"]
autoreload = ["tokio-stream/net", "dep:listenfd"]
health = ["dep:tonic-health"]
grpc-web = ["dep:tonic-web", "dep:bytes", "dep:http", "dep:hyper", "dep:hyper-util", "dep:tracing-subscriber", "dep:tower"]
grpc-web = ["dep:tonic-web", "dep:bytes", "dep:http", "dep:hyper", "dep:hyper-util", "dep:tracing-subscriber", "dep:tower", "dep:tower-http", "tower-http?/cors"]
tracing = ["dep:tracing", "dep:tracing-subscriber"]
uds = ["tokio-stream/net", "dep:tower", "dep:hyper", "dep:hyper-util"]
streaming = ["tokio-stream", "dep:h2"]
Expand Down
10 changes: 7 additions & 3 deletions examples/src/grpc-web/server.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use tonic::{transport::Server, Request, Response, Status};
use tonic::{service::LayerExt as _, transport::Server, Request, Response, Status};

use hello_world::greeter_server::{Greeter, GreeterServer};
use hello_world::{HelloReply, HelloRequest};
Expand Down Expand Up @@ -32,14 +32,18 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let addr = "127.0.0.1:3000".parse().unwrap();

let greeter = MyGreeter::default();
let greeter = GreeterServer::new(greeter);
let greeter = tower::ServiceBuilder::new()
.layer(tower_http::cors::CorsLayer::new())
.layer(tonic_web::GrpcWebLayer::new())
.into_inner()
.named_layer(GreeterServer::new(greeter));

println!("GreeterServer listening on {}", addr);

Server::builder()
// GrpcWeb is over http1 so we must enable it.
.accept_http1(true)
.add_service(tonic_web::enable(greeter))
.add_service(greeter)
.serve(addr)
.await?;

Expand Down
93 changes: 93 additions & 0 deletions tonic/src/service/layered.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
use std::{
marker::PhantomData,
task::{Context, Poll},
};

use tower_layer::Layer;
use tower_service::Service;

use crate::server::NamedService;

/// A layered service to propagate [`NamedService`] implementation.
#[derive(Debug, Clone)]
pub struct Layered<S, T> {
inner: S,
_ty: PhantomData<T>,
}

impl<S, T: NamedService> NamedService for Layered<S, T> {
const NAME: &'static str = T::NAME;
}

impl<Req, S, T> Service<Req> for Layered<S, T>
where
S: Service<Req>,
{
type Response = S::Response;
type Error = S::Error;
type Future = S::Future;

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

fn call(&mut self, req: Req) -> Self::Future {
self.inner.call(req)
}
}

/// Extension trait which adds utility methods to types which implement [`tower_layer::Layer`].
pub trait LayerExt<L>: sealed::Sealed {
/// Applies the layer to a service and wraps it in [`Layered`].
fn named_layer<S>(&self, service: S) -> Layered<L::Service, S>
where
L: Layer<S>;
}

impl<L> LayerExt<L> for L {
fn named_layer<S>(&self, service: S) -> Layered<<L>::Service, S>
where
L: Layer<S>,
{
Layered {
inner: self.layer(service),
_ty: PhantomData,
}
}
}

mod sealed {
pub trait Sealed {}
impl<T> Sealed for T {}
}

#[cfg(test)]
mod tests {
use super::*;

#[derive(Debug, Default)]
struct TestService {}

const TEST_SERVICE_NAME: &str = "test-service-name";

impl NamedService for TestService {
const NAME: &'static str = TEST_SERVICE_NAME;
}

// Checks if the argument implements `NamedService` and returns the implemented `NAME`.
fn get_name_of_named_service<S: NamedService>(_s: &S) -> &'static str {
S::NAME
}

#[test]
fn named_service_is_propagated_to_layered() {
use std::time::Duration;
use tower::{limit::ConcurrencyLimitLayer, timeout::TimeoutLayer};

let layered = TimeoutLayer::new(Duration::from_secs(5)).named_layer(TestService::default());
assert_eq!(get_name_of_named_service(&layered), TEST_SERVICE_NAME);

let layered = ConcurrencyLimitLayer::new(3).named_layer(layered);
assert_eq!(get_name_of_named_service(&layered), TEST_SERVICE_NAME);
}
}
2 changes: 2 additions & 0 deletions tonic/src/service/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
//! Utilities for using Tower services with Tonic.

pub mod interceptor;
pub(crate) mod layered;
#[cfg(feature = "router")]
pub(crate) mod router;

#[doc(inline)]
pub use self::interceptor::{interceptor, Interceptor};
pub use self::layered::{LayerExt, Layered};
#[doc(inline)]
#[cfg(feature = "router")]
pub use self::router::{Routes, RoutesBuilder};
Expand Down

0 comments on commit 9b74abf

Please sign in to comment.