diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 99ea5e1..20c15ac 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,4 +28,4 @@ jobs: - name: Check with transport w/ rustls run: cargo check -p tonic --features rustls - name: Run tests - run: cargo test --all + run: cargo test --all --all-features diff --git a/tonic-build/src/client.rs b/tonic-build/src/client.rs index 7894c53..9d9c044 100644 --- a/tonic-build/src/client.rs +++ b/tonic-build/src/client.rs @@ -1,4 +1,4 @@ -use crate::{generate_doc_comment, generate_doc_comments}; +use crate::generate_doc_comments; use proc_macro2::TokenStream; use prost_build::{Method, Service}; use quote::{format_ident, quote}; @@ -52,12 +52,6 @@ pub(crate) fn generate(service: &Service, proto: &str) -> TokenStream { #[cfg(feature = "transport")] fn generate_connect(service_ident: &syn::Ident) -> TokenStream { - let doc_example = format!( - "let client = {}::connect(\"http://[::1]:50051\")?;", - service_ident - ); - let doc_example = generate_doc_comment(&doc_example); - quote! { impl #service_ident { /// Attempt to create a new client by connecting to a given endpoint. @@ -66,7 +60,7 @@ fn generate_connect(service_ident: &syn::Ident) -> TokenStream { D: std::convert::TryInto, D::Error: Into, { - tonic::transport::Channel::builder().build(dst).map(|c| Self::new(c)) + tonic::transport::Endpoint::new(dst).map(|c| Self::new(c.channel())) } } } diff --git a/tonic-interop/src/bin/client.rs b/tonic-interop/src/bin/client.rs index d175676..2012df6 100644 --- a/tonic-interop/src/bin/client.rs +++ b/tonic-interop/src/bin/client.rs @@ -36,7 +36,7 @@ async fn main() -> Result<(), Box> { endpoint.openssl_tls(ca, Some("foo.test.google.fr".into())); } - let channel = endpoint.channel()?; + let channel = endpoint.channel(); let mut client = client::TestClient::new(channel.clone()); let mut unimplemented_client = client::UnimplementedClient::new(channel); diff --git a/tonic-interop/src/client.rs b/tonic-interop/src/client.rs index c460478..351a691 100644 --- a/tonic-interop/src/client.rs +++ b/tonic-interop/src/client.rs @@ -15,30 +15,6 @@ const TEST_STATUS_MESSAGE: &'static str = "test status message"; const SPECIAL_TEST_STATUS_MESSAGE: &'static str = "\t\ntest with whitespace\r\nand Unicode BMP ☺ and non-BMP 😈\t\n"; -pub async fn create(origin: http::Uri) -> Result> { - // let ca = tokio::fs::read("tonic-interop/data/ca.pem").await?; - - let svc = Channel::builder() - // .tls(ca) - // .tls_override_domain("foo.test.google.fr") - .build(origin)?; - - Ok(TestServiceClient::new(svc)) -} - -pub async fn create_unimplemented( - origin: http::Uri, -) -> Result> { - // let ca = tokio::fs::read("tonic-interop/data/ca.pem").await?; - - let svc = Channel::builder() - // .tls(ca) - // .tls_override_domain("foo.test.google.fr") - .build(origin)?; - - Ok(UnimplementedServiceClient::new(svc)) -} - pub async fn empty_unary(client: &mut TestClient, assertions: &mut Vec) { let result = client.empty_call(Request::new(Empty {})).await; diff --git a/tonic/src/transport/channel.rs b/tonic/src/transport/channel.rs index df26e61..ffa2e08 100644 --- a/tonic/src/transport/channel.rs +++ b/tonic/src/transport/channel.rs @@ -1,32 +1,32 @@ +//! Client implementation and builder. + use super::{ - service::{BoxService, Connection, ServiceList}, + service::{Connection, ServiceList}, Endpoint, }; use crate::{body::BoxBody, client::GrpcService}; -use futures_util::try_future::{MapErr, TryFutureExt}; -use hyper::{Request, Response}; +use bytes::Bytes; +use http::{ + uri::{InvalidUriBytes, Uri}, + Request, Response, +}; use std::{ - convert::TryInto, fmt, future::Future, pin::Pin, task::{Context, Poll}, }; -use tower::buffer::{future::ResponseFuture, Buffer}; -use tower::discover::Discover; +use tower::{ + buffer::{self, Buffer}, + discover::Discover, + util::{BoxService, Either}, + Service, +}; use tower_balance::p2c::Balance; -use tower_service::Service; -type BoxFuture<'a, T> = Pin + Send + 'a>>; -type Inner = Box< - dyn Service< - Request, - Response = Response, - Error = crate::Error, - Future = BoxFuture<'static, Result, crate::Error>>, - > + Send - + 'static, ->; +type Svc = Either, Response, crate::Error>>; + +const DEFAULT_BUFFER_SIZE: usize = 1024; /// A default batteries included `transport` channel. /// @@ -34,67 +34,73 @@ type Inner = Box< /// and `tower` services. #[derive(Clone)] pub struct Channel { - svc: Buffer>, + svc: Buffer>, +} + +/// A future that resolves to an HTTP response. +/// +/// This is returned by the `Service::call` on [`Channel`]. +pub struct ResponseFuture { + inner: buffer::future::ResponseFuture<>>::Future>, } impl Channel { - /// Create a [`Builder`] that can create a [`Channel`]. - pub fn builder() -> Builder { - Builder::new() - } -} - -impl GrpcService for Channel { - type ResponseBody = hyper::Body; - type Error = super::Error; - - type Future = MapErr< - ResponseFuture, crate::Error>>>, - fn(crate::Error) -> super::Error, - >; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - GrpcService::poll_ready(&mut self.svc, cx) - .map_err(|e| super::Error::from((super::ErrorKind::Client, e))) + /// Create a [`Endpoint`] builder that can create a [`Channel`]'s. + pub fn builder(uri: Uri) -> Endpoint { + Endpoint::from(uri) } - fn call(&mut self, request: Request) -> Self::Future { - GrpcService::call(&mut self.svc, request) - .map_err(|e| super::Error::from((super::ErrorKind::Client, e))) - } -} - -#[derive(Debug)] -pub struct Builder { - ca: Option>, - override_domain: Option, - buffer_size: usize, - balance: Option, -} - -impl Builder { - fn new() -> Self { - Self { - ca: None, - override_domain: None, - buffer_size: 1024, - balance: None, - } + /// Create an `Endpoint` from a static string. + /// + /// ``` + /// # use tonic::transport::Channel; + /// Channel::from_static("https://example.com"); + /// ``` + pub fn from_static(s: &'static str) -> Endpoint { + let uri = Uri::from_static(s); + Self::builder(uri) } - /// Set the buffer size for when the inner client applies back pressure and - /// can no longer accept requests. Defaults to `1024`. - pub fn buffer(&mut self, size: usize) -> &mut Self { - self.buffer_size = size; - self + /// Create an `Endpoint` from shared bytes. + /// + /// ``` + /// # use tonic::transport::Channel; + /// Channel::from_shared("https://example.com"); + /// ``` + pub fn from_shared(s: impl Into) -> Result { + let uri = Uri::from_shared(s.into())?; + Ok(Self::builder(uri)) } - pub fn balance_list(&mut self, list: Vec) -> Result { + /// Balance a list of [`Endpoint`]'s. + /// + /// This creates a [`Channel`] that will load balance accross all the + /// provided endpoints. + pub fn balance_list(list: impl Iterator) -> Self { + let list = list.collect::>(); + + let buffer_size = list + .iter() + .next() + .and_then(|e| e.buffer_size) + .unwrap_or(DEFAULT_BUFFER_SIZE); + let discover = ServiceList::new(list); - self.balance(discover) + + Self::balance(discover, buffer_size) } - fn balance(&mut self, discover: D) -> Result + pub(crate) fn connect(endpoint: Endpoint) -> Self { + let buffer_size = endpoint.buffer_size.clone().unwrap_or(DEFAULT_BUFFER_SIZE); + + let svc = Connection::new(endpoint); + + let svc = Buffer::new(Either::A(svc), buffer_size); + + Channel { svc } + } + + pub(crate) fn balance(discover: D, buffer_size: usize) -> Self where D: Discover + Unpin + Send + 'static, D::Error: Into, @@ -103,25 +109,35 @@ impl Builder { let svc = Balance::from_entropy(discover); let svc = BoxService::new(svc); - let svc = Buffer::new(Box::new(svc) as Inner, 100); + let svc = Buffer::new(Either::B(svc), buffer_size); - Ok(Channel { svc }) + Channel { svc } + } +} + +impl GrpcService for Channel { + type ResponseBody = hyper::Body; + type Error = super::Error; + type Future = ResponseFuture; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + GrpcService::poll_ready(&mut self.svc, cx) + .map_err(|e| super::Error::from_source(super::ErrorKind::Client, e)) } - pub fn connect(&mut self, endpoint: Endpoint) -> Result { - self.balance_list(vec![endpoint]) + fn call(&mut self, request: Request) -> Self::Future { + let inner = GrpcService::call(&mut self.svc, request); + ResponseFuture { inner } } +} - pub fn build(&mut self, uri: T) -> Result - where - T: TryInto, - T::Error: Into, - { - let uri = uri - .try_into() - .map_err(|e| super::Error::from((super::ErrorKind::Client, e.into())))?; +impl Future for ResponseFuture { + type Output = Result, super::Error>; - self.balance_list(vec![uri.into()]) + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let val = futures_util::ready!(Pin::new(&mut self.inner).poll(cx)) + .map_err(|e| super::Error::from_source(super::ErrorKind::Client, e))?; + Ok(val).into() } } @@ -130,3 +146,9 @@ impl fmt::Debug for Channel { f.debug_struct("Channel").finish() } } + +impl fmt::Debug for ResponseFuture { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ResponseFuture").finish() + } +} diff --git a/tonic/src/transport/endpoint.rs b/tonic/src/transport/endpoint.rs index c28abed..3dd18ac 100644 --- a/tonic/src/transport/endpoint.rs +++ b/tonic/src/transport/endpoint.rs @@ -3,8 +3,14 @@ use super::channel::Channel; use super::{service::TlsConnector, tls::Certificate}; use bytes::Bytes; use http::uri::{InvalidUriBytes, Uri}; -use std::{convert::TryFrom, time::Duration}; +use std::{ + convert::{TryFrom, TryInto}, + time::Duration, +}; +/// Channel builder. +/// +/// This struct is used to build and configure HTTP/2 channels. #[derive(Debug, Clone)] pub struct Endpoint { pub(super) uri: Uri, @@ -13,54 +19,131 @@ pub struct Endpoint { pub(super) rate_limit: Option<(u64, Duration)>, #[cfg(feature = "tls")] pub(super) tls: Option, + pub(super) buffer_size: Option, } impl Endpoint { + // TODO: determine if we want to expose this or not. This is really + // just used in codegen for a shortcut. + #[doc(hidden)] + pub fn new(dst: D) -> Result + where + D: TryInto, + D::Error: Into, + { + let me = dst + .try_into() + .map_err(|e| super::Error::from_source(super::ErrorKind::Client, e.into()))?; + Ok(me) + } + + /// Convert an `Endpoint` from a static string. + /// + /// ``` + /// # use tonic::transport::Endpoint; + /// Endpoint::from_static("https://example.com"); + /// ``` pub fn from_static(s: &'static str) -> Self { let uri = Uri::from_static(s); Self::from(uri) } + /// Convert an `Endpoint` from shared bytes. + /// + /// ``` + /// # use tonic::transport::Endpoint; + /// Endpoint::from_shared("https://example.com".to_string()); + /// ``` pub fn from_shared(s: impl Into) -> Result { let uri = Uri::from_shared(s.into())?; Ok(Self::from(uri)) } + /// Apply a timeout to each request. + /// + /// ``` + /// # use tonic::transport::Endpoint; + /// # use std::time::Duration; + /// # let mut builder = Endpoint::from_static("https://example.com"); + /// builder.timeout(Duration::from_secs(5)); + /// ``` pub fn timeout(&mut self, dur: Duration) -> &mut Self { self.timeout = Some(dur); self } + /// Apply a concurrency limit to each request. + /// + /// ``` + /// # use tonic::transport::Endpoint; + /// # let mut builder = Endpoint::from_static("https://example.com"); + /// builder.concurrency_limit(256); + /// ``` pub fn concurrency_limit(&mut self, limit: usize) -> &mut Self { self.concurrency_limit = Some(limit); self } + /// Apply a rate limit to each request. + /// + /// ``` + /// # use tonic::transport::Endpoint; + /// # use std::time::Duration; + /// # let mut builder = Endpoint::from_static("https://example.com"); + /// builder.rate_limit(32, Duration::from_secs(1)); + /// ``` pub fn rate_limit(&mut self, limit: u64, duration: Duration) -> &mut Self { self.rate_limit = Some((limit, duration)); self } + /// ```no_run + /// # use tonic::transport::{Certificate, Endpoint}; + /// # fn dothing() -> Result<(), Box> { + /// # let mut builder = Endpoint::from_static("https://example.com"); + /// let ca = std::fs::read_to_string("ca.pem")?; + /// + /// let ca = Certificate::from_pem(ca); + /// + /// builder.openssl_tls(ca, "example.com".to_string()); + /// # Ok(()) + /// # } + /// ``` #[cfg(feature = "openssl")] - pub fn openssl_tls(&mut self, ca: Certificate, domain: Option) -> &mut Self { - let domain = domain.unwrap_or_else(|| self.uri.clone().to_string()); + pub fn openssl_tls(&mut self, ca: Certificate, domain: impl Into>) -> &mut Self { + let domain = domain + .into() + .unwrap_or_else(|| self.uri.clone().to_string()); let tls = TlsConnector::new_with_openssl(ca, domain).unwrap(); self.tls = Some(tls); self } + /// ```no_run + /// # use tonic::transport::{Certificate, Endpoint}; + /// # fn dothing() -> Result<(), Box> { + /// # let mut builder = Endpoint::from_static("https://example.com"); + /// let ca = std::fs::read_to_string("ca.pem")?; + /// + /// let ca = Certificate::from_pem(ca); + /// + /// builder.rustls_tls(ca, "example.com".to_string()); + /// # Ok(()) + /// # } + /// ``` #[cfg(feature = "rustls")] - pub fn rustls_tls(&mut self, ca: Certificate, domain: Option) -> &mut Self { - let domain = domain.unwrap_or_else(|| self.uri.clone().to_string()); + pub fn rustls_tls(&mut self, ca: Certificate, domain: impl Into>) -> &mut Self { + let domain = domain + .into() + .unwrap_or_else(|| self.uri.clone().to_string()); let tls = TlsConnector::new_with_rustls(ca, domain).unwrap(); self.tls = Some(tls); self } - // pub fn metadata_interceptor(f: impl Fn(MetadataMap) ->) - - pub fn channel(&self) -> Result { - Channel::builder().connect(self.clone()) + /// Create a channel from this config. + pub fn channel(&self) -> Channel { + Channel::connect(self.clone()) } } @@ -73,6 +156,7 @@ impl From for Endpoint { timeout: None, #[cfg(feature = "tls")] tls: None, + buffer_size: None, } } } diff --git a/tonic/src/transport/error.rs b/tonic/src/transport/error.rs index d057f40..e5b20c1 100644 --- a/tonic/src/transport/error.rs +++ b/tonic/src/transport/error.rs @@ -1,34 +1,26 @@ use std::{error, fmt}; +/// Error's that originate from the client or server; pub struct Error { kind: ErrorKind, source: Option, } +impl Error { + pub(crate) fn from_source(kind: ErrorKind, source: crate::Error) -> Self { + Self { + kind, + source: Some(source), + } + } +} + #[derive(Debug)] pub(crate) enum ErrorKind { Client, Server, } -impl From for Error { - fn from(t: ErrorKind) -> Self { - Self { - kind: t, - source: None, - } - } -} - -impl From<(ErrorKind, crate::Error)> for Error { - fn from(t: (ErrorKind, crate::Error)) -> Self { - Self { - kind: t.0, - source: Some(t.1), - } - } -} - impl fmt::Debug for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut f = f.debug_tuple("Error"); diff --git a/tonic/src/transport/mod.rs b/tonic/src/transport/mod.rs index 03a96a0..d963186 100644 --- a/tonic/src/transport/mod.rs +++ b/tonic/src/transport/mod.rs @@ -1,6 +1,76 @@ -#![allow(missing_docs)] - -//! TODO: write transport docs. +//! Batteries included server and client. +//! +//! This module provides a set of batteries included, fully featured and +//! fast set of HTTP/2 server and client's. These components each provide either an +//! `openssl` or `rustls` tls backend when the respective feature flags are enabled. +//!They also provide may configurable knobs that can be used to tune how they work. +//! +//! # Features +//! +//! - TLS support via either [OpenSSL] or [rustls]. +//! - Load balancing +//! - Timeouts +//! - Concurrency Limits +//! - Rate limiting +//! - gRPC Interceptors +//! +//! # Examples +//! +//! ## Client +//! +//! ```no_run +//! # use tonic::transport::{Channel, Certificate}; +//! # use std::time::Duration; +//! # use tonic::body::BoxBody; +//! # use tonic::client::GrpcService;; +//! # use http::Request; +//! # #[cfg(feature = "rustls")] +//! # async fn do_thing() -> Result<(), Box> { +//! let cert = std::fs::read_to_string("ca.pem")?; +//! +//! let mut channel = Channel::from_static("https://example.com") +//! .rustls_tls(Certificate::from_pem(&cert), "example.com".to_string()) +//! .timeout(Duration::from_secs(5)) +//! .rate_limit(5, Duration::from_secs(1)) +//! .concurrency_limit(256) +//! .channel(); +//! +//! channel.call(Request::new(BoxBody::empty())).await?; +//! # Ok(()) +//! # } +//! ``` +//! +//! ## Server +//! +//! ```no_run +//! # use tonic::transport::{Server, Identity}; +//! # use tower::{Service, service_fn}; +//! # use futures_util::future::{err, ok}; +//! # #[cfg(feature = "rustls")] +//! # async fn do_thing() -> Result<(), Box> { +//! # let my_svc = service_fn(|_| ok::<_, tonic::Status>(service_fn(|req| err(tonic::Status::unimplemented(""))))); +//! let cert = std::fs::read_to_string("server.pem")?; +//! let key = std::fs::read_to_string("server.key")?; +//! +//! let addr = "[::1]:50051".parse()?; +//! +//! Server::builder() +//! .rustls_tls(Identity::from_pem(&cert, &key)) +//! .concurrency_limit_per_connection(256) +//! .interceptor_fn(|svc, req| { +//! println!("Request: {:?}", req); +//! svc.call(req) +//! }) +//! .clone() +//! .serve(addr, my_svc) +//! .await?; +//! +//! # Ok(()) +//! # } +//! ``` +//! +//! [OpenSSL]: https://www.openssl.org/ +//! [rustls]: https://docs.rs/rustls/0.16.0/rustls/ pub mod channel; pub mod server; @@ -10,9 +80,11 @@ mod error; mod service; mod tls; +#[doc(inline)] pub use self::channel::Channel; pub use self::endpoint::Endpoint; pub use self::error::Error; +#[doc(inline)] pub use self::server::Server; pub use self::tls::{Certificate, Identity}; pub use hyper::Body; diff --git a/tonic/src/transport/server.rs b/tonic/src/transport/server.rs index e552b05..4da37fc 100644 --- a/tonic/src/transport/server.rs +++ b/tonic/src/transport/server.rs @@ -1,12 +1,16 @@ -use super::service::{layer_fn, BoxedIo}; +//! Server implementation and builder. + +use super::service::{layer_fn, BoxedIo, ServiceBuilderExt}; #[cfg(feature = "tls")] use super::{service::TlsAcceptor, tls::Identity}; use crate::body::BoxBody; use futures_core::Stream; use futures_util::{ready, try_future::MapErr, TryFutureExt, TryStreamExt}; use http::{Request, Response}; -use hyper::server::{accept::Accept, conn}; -use hyper::Body; +use hyper::{ + server::{accept::Accept, conn}, + Body, +}; use std::{ fmt, future::Future, @@ -14,12 +18,16 @@ use std::{ pin::Pin, sync::Arc, task::{Context, Poll}, + // time::Duration, +}; +use tower::{ + layer::{util::Stack, Layer}, + limit::concurrency::ConcurrencyLimitLayer, + // timeout::TimeoutLayer, + Service, + ServiceBuilder, }; -use tower::layer::util::Stack; -use tower::layer::Layer; -use tower::util::Either; use tower_make::MakeService; -use tower_service::Service; type BoxService = tower::util::BoxService, Response, crate::Error>; type Interceptor = Arc + Send + Sync + 'static>; @@ -27,38 +35,43 @@ type Interceptor = Arc + Send + Sync /// A default batteries included `transport` server. /// /// This is a wrapper around [`hyper::Server`] and provides an easy builder -/// pattern style [`Builder`]. This builder exposes easy configuration parameters +/// pattern style builder [`Server`]. This builder exposes easy configuration parameters /// for providing a fully featured http2 based gRPC server. This should provide /// a very good out of the box http2 server for use with tonic but is also a /// reference implementation that should be a good starting point for anyone /// wanting to create a more complex and/or specific implementation. -#[derive(Debug)] +#[derive(Default, Clone)] pub struct Server { - _p: (), -} - -impl Server { - /// Create a new [`Builder`] that can configure a Server. - pub fn builder() -> Builder { - Builder::new() - } -} - -/// -#[derive(Default)] -pub struct Builder { interceptor: Option, - // concurrency_limit: Option, + concurrency_limit: Option, + // timeout: Option, #[cfg(feature = "tls")] tls: Option, } -impl Builder { - fn new() -> Self { +impl Server { + /// Create a new server builder that can configure a [`Server`]. + pub fn builder() -> Self { Default::default() } +} - /// Add a tls cert. +impl Server { + /// Set the [`Identity`] of this server using `openssl`. + /// + /// ```no_run + /// # use tonic::transport::{Identity, Server}; + /// # fn dothing() -> Result<(), Box> { + /// # let mut builder = Server::builder(); + /// let cert = std::fs::read_to_string("server.pem")?; + /// let key = std::fs::read_to_string("server.key")?; + /// + /// let identity = Identity::from_pem(&cert, &key); + /// + /// builder.openssl_tls(identity); + /// # Ok(()) + /// # } + /// ``` #[cfg(feature = "openssl")] pub fn openssl_tls(&mut self, identity: Identity) -> &mut Self { let acceptor = TlsAcceptor::new_with_openssl(identity).unwrap(); @@ -66,6 +79,21 @@ impl Builder { self } + /// Set the [`Identity`] of this server using `rustls`. + /// + /// ```no_run + /// # use tonic::transport::{Identity, Server}; + /// # fn dothing() -> Result<(), Box> { + /// # let mut builder = Server::builder(); + /// let cert = std::fs::read_to_string("server.pem")?; + /// let key = std::fs::read_to_string("server.key")?; + /// + /// let identity = Identity::from_pem(&cert, &key); + /// + /// builder.rustls_tls(identity); + /// # Ok(()) + /// # } + /// ``` #[cfg(feature = "rustls")] pub fn rustls_tls(&mut self, identity: Identity) -> &mut Self { let acceptor = TlsAcceptor::new_with_rustls(identity).unwrap(); @@ -73,13 +101,37 @@ impl Builder { self } - // FIXME: add server side layering ability - // pub fn concurrency_limit(&mut self, limit: usize) -> &mut Self { - // self.concurrency_limit = Some(limit); + /// Set the concurrency limit applied to on requests inbound per connection. + /// + /// ``` + /// # use tonic::transport::Server; + /// # use tower_service::Service; + /// # let mut builder = Server::builder(); + /// builder.concurrency_limit_per_connection(32); + /// ``` + pub fn concurrency_limit_per_connection(&mut self, limit: usize) -> &mut Self { + self.concurrency_limit = Some(limit); + self + } + + // FIXME: tower-timeout currentlly uses `From` instead of `Into` for the error + // so our services do not align. + // pub fn timeout(&mut self, timeout: Duration) -> &mut Self { + // self.timeout = Some(timeout); // self // } /// Intercept the execution of gRPC methods. + /// + /// ``` + /// # use tonic::transport::Server; + /// # use tower_service::Service; + /// # let mut builder = Server::builder(); + /// builder.interceptor_fn(|svc, req| { + /// println!("request={:?}", req); + /// svc.call(req) + /// }); + /// ``` pub fn interceptor_fn(&mut self, f: F) -> &mut Self where F: Fn(&mut BoxService, Request) -> Out + Send + Sync + 'static, @@ -95,6 +147,8 @@ impl Builder { self } + /// Consume this [`Server`] creating a future that will execute the server + /// on [`tokio`]'s default executor. pub async fn serve(self, addr: SocketAddr, svc: M) -> Result<(), super::Error> where M: Service<(), Response = S>, @@ -105,6 +159,8 @@ impl Builder { S::Error: Into + Send, { let interceptor = self.interceptor.clone(); + let concurrency_limit = self.concurrency_limit.clone(); + // let timeout = self.timeout.clone(); let incoming = hyper::server::accept::from_stream(async_stream::try_stream! { let mut tcp = TcpIncoming::bind(addr)?; @@ -126,6 +182,8 @@ impl Builder { let svc = MakeSvc { inner: svc, interceptor, + concurrency_limit, + // timeout, }; hyper::Server::builder(incoming) @@ -139,10 +197,10 @@ impl Builder { } fn map_err(e: impl Into) -> super::Error { - (super::ErrorKind::Server, e.into()).into() + super::Error::from_source(super::ErrorKind::Server, e.into()) } -impl fmt::Debug for Builder { +impl fmt::Debug for Server { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Builder").finish() } @@ -197,6 +255,8 @@ where struct MakeSvc { interceptor: Option, + concurrency_limit: Option, + // timeout: Option, inner: M, } @@ -209,7 +269,7 @@ where S::Future: Send + 'static, S::Error: Into + Send, { - type Response = Either, BoxService>; + type Response = BoxService; type Error = crate::Error; type Future = Pin> + Send + 'static>>; @@ -221,16 +281,25 @@ where fn call(&mut self, _: T) -> Self::Future { let interceptor = self.interceptor.clone(); let make = self.inner.make_service(()); + let concurrency_limit = self.concurrency_limit.clone(); + // let timeout = self.timeout.clone(); Box::pin(async move { let svc = make.await.map_err(Into::into)?; - if let Some(interceptor) = interceptor { + let svc = ServiceBuilder::new() + .optional_layer(concurrency_limit.map(ConcurrencyLimitLayer::new)) + // .optional_layer(timeout.map(TimeoutLayer::new)) + .service(svc); + + let svc = if let Some(interceptor) = interceptor { let layered = interceptor.layer(BoxService::new(Svc(svc))); - Ok(Either::B(layered)) + BoxService::new(Svc(layered)) } else { - Ok(Either::A(Svc(svc))) - } + BoxService::new(Svc(svc)) + }; + + Ok(svc) }) } } diff --git a/tonic/src/transport/service/boxed.rs b/tonic/src/transport/service/boxed.rs deleted file mode 100644 index 67e9a7b..0000000 --- a/tonic/src/transport/service/boxed.rs +++ /dev/null @@ -1,38 +0,0 @@ -use std::{ - future::Future, - pin::Pin, - task::{Context, Poll}, -}; -use tower_service::Service; - -#[derive(Debug, Clone)] -pub(crate) struct BoxService { - inner: S, -} - -impl BoxService { - pub(crate) fn new(inner: S) -> Self { - Self { inner } - } -} - -impl Service for BoxService -where - S: Service, - S::Future: Send + 'static, -{ - type Response = S::Response; - type Error = S::Error; - - type Future = - Pin> + Send + 'static>>; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.inner.poll_ready(cx) - } - - fn call(&mut self, request: Request) -> Self::Future { - let fut = self.inner.call(request); - Box::pin(fut) - } -} diff --git a/tonic/src/transport/service/connection.rs b/tonic/src/transport/service/connection.rs index 1e0c81d..f9fc58b 100644 --- a/tonic/src/transport/service/connection.rs +++ b/tonic/src/transport/service/connection.rs @@ -22,12 +22,12 @@ use tower_service::Service; pub(crate) type Request = http::Request; pub(crate) type Response = http::Response; -pub struct Connection { +pub(crate) struct Connection { inner: BoxService, } impl Connection { - pub fn new(endpoint: Endpoint) -> Result { + pub(crate) fn new(endpoint: Endpoint) -> Self { #[cfg(feature = "tls")] let connector = connector(endpoint.tls.clone()); @@ -51,9 +51,9 @@ impl Connection { let inner = stack.layer(conn); - Ok(Self { + Self { inner: BoxService::new(inner), - }) + } } } diff --git a/tonic/src/transport/service/discover.rs b/tonic/src/transport/service/discover.rs index 154ef59..1f6e61a 100644 --- a/tonic/src/transport/service/discover.rs +++ b/tonic/src/transport/service/discover.rs @@ -6,13 +6,13 @@ use std::task::{Context, Poll}; use tower::discover::{Change, Discover}; #[derive(Debug)] -pub struct ServiceList { +pub(crate) struct ServiceList { list: VecDeque, i: usize, } impl ServiceList { - pub fn new(list: Vec) -> Self { + pub(crate) fn new(list: Vec) -> Self { Self { list: list.into(), i: 0, @@ -34,10 +34,10 @@ impl Discover for ServiceList { let i = self.i; self.i += 1; - match Connection::new(endpoint) { - Ok(svc) => Poll::Ready(Ok(Change::Insert(i, svc))), - Err(e) => Poll::Ready(Err(e)), - } + let svc = Connection::new(endpoint); + let change = Ok(Change::Insert(i, svc)); + + Poll::Ready(change) } None => Poll::Pending, } diff --git a/tonic/src/transport/service/either.rs b/tonic/src/transport/service/either.rs new file mode 100644 index 0000000..b4eab65 --- /dev/null +++ b/tonic/src/transport/service/either.rs @@ -0,0 +1,72 @@ +use futures_util::try_future::{MapErr, TryFutureExt}; +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; +use tower::Service; + +pub(crate) enum Either { + A(A), + B(B), +} + +impl Service for Either +where + A: Service, + B: Service, + A::Error: Into, + B::Error: Into, +{ + type Response = Response; + type Error = crate::Error; + type Future = Either< + MapErr crate::Error>, + MapErr crate::Error>, + >; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + match self { + Either::A(svc) => svc.poll_ready(cx).map_err(Into::into), + Either::B(svc) => svc.poll_ready(cx).map_err(Into::into), + } + } + + fn call(&mut self, req: Request) -> Self::Future { + match self { + Either::A(svc) => { + let fut = svc + .call(req) + .map_err((|e| e.into()) as fn(A::Error) -> crate::Error); + Either::A(fut) + } + + Either::B(svc) => { + let fut = svc + .call(req) + .map_err((|e| e.into()) as fn(B::Error) -> crate::Error); + Either::B(fut) + } + } + } +} + +impl Unpin for Either {} + +impl Future for Either +where + A: Future, + B: Future, +{ + type Output = A::Output; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // safe because we do not exposed an unchecked mut beyond this projection. + let mut me = unsafe { self.get_unchecked_mut() }; + + match &mut me { + Either::A(fut) => unsafe { Pin::new_unchecked(fut) }.poll(cx), + Either::B(fut) => unsafe { Pin::new_unchecked(fut) }.poll(cx), + } + } +} diff --git a/tonic/src/transport/service/layer.rs b/tonic/src/transport/service/layer.rs index 4a8c11e..d3aec3d 100644 --- a/tonic/src/transport/service/layer.rs +++ b/tonic/src/transport/service/layer.rs @@ -1,6 +1,6 @@ +use super::either::Either; use tower::{ layer::{util::Stack, Layer}, - util::Either, ServiceBuilder, }; pub(crate) trait ServiceBuilderExt { diff --git a/tonic/src/transport/service/mod.rs b/tonic/src/transport/service/mod.rs index c252c0d..3ee2cba 100644 --- a/tonic/src/transport/service/mod.rs +++ b/tonic/src/transport/service/mod.rs @@ -1,19 +1,18 @@ mod add_origin; -mod boxed; mod connection; mod connector; mod discover; +mod either; mod io; mod layer; #[cfg(feature = "tls")] mod tls; pub(crate) use self::add_origin::AddOrigin; -pub(crate) use self::boxed::BoxService; pub(crate) use self::connection::Connection; pub(crate) use self::connector::connector; pub(crate) use self::discover::ServiceList; pub(crate) use self::io::BoxedIo; -pub(crate) use self::layer::layer_fn; +pub(crate) use self::layer::{layer_fn, ServiceBuilderExt}; #[cfg(feature = "tls")] pub(crate) use self::tls::{TlsAcceptor, TlsConnector}; diff --git a/tonic/src/transport/tls.rs b/tonic/src/transport/tls.rs index a08b156..f8fb991 100644 --- a/tonic/src/transport/tls.rs +++ b/tonic/src/transport/tls.rs @@ -1,8 +1,10 @@ +/// Represents a X509 certificate. #[derive(Debug, Clone)] pub struct Certificate { pub(crate) pem: Vec, } +/// Represents a private key and X509 certificate. #[derive(Debug, Clone)] pub struct Identity { pub(crate) cert: Certificate, @@ -10,14 +12,22 @@ pub struct Identity { } impl Certificate { - pub fn from_pem(pem: Vec) -> Self { + /// Parse a PEM encoded X509 Certificate. + /// + /// The provided PEM should include at least one PEM encoded certificate. + pub fn from_pem(pem: impl AsRef<[u8]>) -> Self { + let pem = pem.as_ref().into(); Self { pem } } } impl Identity { - pub fn from_pem(cert: Vec, key: Vec) -> Self { + /// Parse a PEM encoded certificate and private key. + /// + /// The provided cert must contain at least one PEM encoded certificate. + pub fn from_pem(cert: impl AsRef<[u8]>, key: impl AsRef<[u8]>) -> Self { let cert = Certificate::from_pem(cert); + let key = key.as_ref().into(); Self { cert, key } } }