From 0c4fd0b6bd78d714805376bfa2163b0ab35082e8 Mon Sep 17 00:00:00 2001 From: Cristiano Piemontese Date: Thu, 25 Jan 2024 15:58:21 +0100 Subject: [PATCH 01/20] Update README.md (#1607) Groundbreaking change that will improve the documentation :rocket: --- tonic-build/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tonic-build/README.md b/tonic-build/README.md index a513d1840..c4e3d5828 100644 --- a/tonic-build/README.md +++ b/tonic-build/README.md @@ -1,6 +1,6 @@ # tonic-build -Compiles proto files via prost and generates service stubs and proto definitiones for use with tonic. +Compiles proto files via prost and generates service stubs and proto definitions for use with tonic. ## Features From a1d6daacc587d7fedc40d4694ff2941dd5c558a9 Mon Sep 17 00:00:00 2001 From: A L Manning <10554686+A-Manning@users.noreply.github.com> Date: Thu, 25 Jan 2024 23:01:17 +0800 Subject: [PATCH 02/20] tonic-reflection: feature-gate server impl (#1605) * tonic-reflection: feature-gate server impl * Update tonic-reflection/Cargo.toml --------- Co-authored-by: Lucio Franco --- tonic-reflection/Cargo.toml | 12 ++++++++++-- tonic-reflection/src/lib.rs | 2 ++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/tonic-reflection/Cargo.toml b/tonic-reflection/Cargo.toml index 6b764354a..302107432 100644 --- a/tonic-reflection/Cargo.toml +++ b/tonic-reflection/Cargo.toml @@ -17,11 +17,19 @@ readme = "README.md" repository = "https://github.com/hyperium/tonic" version = "0.10.2" +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[features] +server = ["dep:tokio", "dep:tokio-stream"] +default = ["server"] + [dependencies] prost = "0.12" prost-types = "0.12" -tokio = {version = "1.0", features = ["sync", "rt"]} -tokio-stream = {version = "0.1", features = ["net"]} +tokio = { version = "1.0", features = ["sync", "rt"], optional = true } +tokio-stream = {version = "0.1", features = ["net"], optional = true } tonic = { version = "0.10", path = "../tonic", default-features = false, features = ["codegen", "prost"] } [dev-dependencies] diff --git a/tonic-reflection/src/lib.rs b/tonic-reflection/src/lib.rs index 01d011083..c15f23229 100644 --- a/tonic-reflection/src/lib.rs +++ b/tonic-reflection/src/lib.rs @@ -43,4 +43,6 @@ pub mod pb { } /// Implementation of the server component of gRPC Server Reflection. +#[cfg(feature = "server")] +#[cfg_attr(docsrs, doc(cfg(feature = "server")))] pub mod server; From f714f42414f50cdcd2cc975b279442cfae87103f Mon Sep 17 00:00:00 2001 From: ngoguey <9285880+Ngoguey42@users.noreply.github.com> Date: Thu, 25 Jan 2024 20:08:16 +0100 Subject: [PATCH 03/20] Repair rust-analyzer for tonic files (#1604) --- tonic-build/src/prost.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/tonic-build/src/prost.rs b/tonic-build/src/prost.rs index 3202e2730..dafbe9814 100644 --- a/tonic-build/src/prost.rs +++ b/tonic-build/src/prost.rs @@ -541,13 +541,9 @@ impl Builder { protos: &[impl AsRef], includes: &[impl AsRef], ) -> io::Result<()> { - let out_dir = if let Some(out_dir) = self.out_dir.as_ref() { - out_dir.clone() - } else { - PathBuf::from(std::env::var("OUT_DIR").unwrap()) - }; - - config.out_dir(out_dir); + if let Some(out_dir) = self.out_dir.as_ref() { + config.out_dir(out_dir); + } if let Some(path) = self.file_descriptor_set_path.as_ref() { config.file_descriptor_set_path(path); } From c30cb783b9e5eaa4ea05003d9cacac733d135c58 Mon Sep 17 00:00:00 2001 From: tottoto Date: Tue, 30 Jan 2024 02:11:37 +0900 Subject: [PATCH 04/20] chore(reflection): Make prost-types dependency optional (#1610) --- tonic-reflection/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tonic-reflection/Cargo.toml b/tonic-reflection/Cargo.toml index 302107432..e411fad5d 100644 --- a/tonic-reflection/Cargo.toml +++ b/tonic-reflection/Cargo.toml @@ -22,12 +22,12 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [features] -server = ["dep:tokio", "dep:tokio-stream"] +server = ["prost-types", "dep:tokio", "dep:tokio-stream"] default = ["server"] [dependencies] prost = "0.12" -prost-types = "0.12" +prost-types = {version = "0.12", optional = true} tokio = { version = "1.0", features = ["sync", "rt"], optional = true } tokio-stream = {version = "0.1", features = ["net"], optional = true } tonic = { version = "0.10", path = "../tonic", default-features = false, features = ["codegen", "prost"] } From 227b16934bf5e5b94e9035c43009713b01379491 Mon Sep 17 00:00:00 2001 From: Lucio Franco Date: Thu, 1 Feb 2024 10:32:03 -0500 Subject: [PATCH 05/20] web: fix invalid bit header error message (#1618) --- tonic-web/src/call.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tonic-web/src/call.rs b/tonic-web/src/call.rs index 731b9f667..f52087e9e 100644 --- a/tonic-web/src/call.rs +++ b/tonic-web/src/call.rs @@ -444,7 +444,10 @@ fn find_trailers(buf: &[u8]) -> Result { } if !(header == 0 || header == 1) { - return Err(Status::internal("Invalid header bit {} expected 0 or 1")); + return Err(Status::internal(format!( + "Invalid header bit {} expected 0 or 1", + header + ))); } let msg_len = temp_buf.get_u32(); From 4859a73f70ea88c37227221f3122b0ca4f81afe0 Mon Sep 17 00:00:00 2001 From: Ben Schofield <47790940+Benjscho@users.noreply.github.com> Date: Thu, 1 Feb 2024 13:34:42 -0800 Subject: [PATCH 06/20] Add connection timeout to connect_with_connector_lazy (#1619) Currently connect_with_connector_lazy doesn't respect connection timeouts. This means that if you have a misbehaving server a request that's connecting lazily can hang for the client without timing out. This commit adds the timeout to the custom connector if it has been set on the endpoint, bringing the behaviour in line with other connect methods. --- tonic/src/transport/channel/endpoint.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tonic/src/transport/channel/endpoint.rs b/tonic/src/transport/channel/endpoint.rs index 6aacb57a5..598e89b70 100644 --- a/tonic/src/transport/channel/endpoint.rs +++ b/tonic/src/transport/channel/endpoint.rs @@ -390,7 +390,13 @@ impl Endpoint { crate::Error: From + Send + 'static, { let connector = self.connector(connector); - Channel::new(connector, self.clone()) + if let Some(connect_timeout) = self.connect_timeout { + let mut connector = hyper_timeout::TimeoutConnector::new(connector); + connector.set_connect_timeout(Some(connect_timeout)); + Channel::new(connector, self.clone()) + } else { + Channel::new(connector, self.clone()) + } } /// Get the endpoint uri. From 42274686befb0440c038e6f53fcc68fdb3dcf51e Mon Sep 17 00:00:00 2001 From: Netanel Rabinowitz Date: Fri, 2 Feb 2024 21:36:50 +0200 Subject: [PATCH 07/20] Update helloworld-tutorial.md - Fix Clippy lints (#1617) warning: useless conversion to the same type: `std::string::String` --- examples/helloworld-tutorial.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/helloworld-tutorial.md b/examples/helloworld-tutorial.md index af1f097cf..8c494db5a 100644 --- a/examples/helloworld-tutorial.md +++ b/examples/helloworld-tutorial.md @@ -167,7 +167,7 @@ impl Greeter for MyGreeter { println!("Got a request: {:?}", request); let reply = hello_world::HelloReply { - message: format!("Hello {}!", request.into_inner().name).into(), // We must use .into_inner() as the fields of gRPC requests and responses are private + message: format!("Hello {}!", request.into_inner().name), // We must use .into_inner() as the fields of gRPC requests and responses are private }; Ok(Response::new(reply)) // Send back our formatted greeting @@ -216,7 +216,7 @@ impl Greeter for MyGreeter { println!("Got a request: {:?}", request); let reply = hello_world::HelloReply { - message: format!("Hello {}!", request.into_inner().name).into(), + message: format!("Hello {}!", request.into_inner().name), }; Ok(Response::new(reply)) From 2a2809b368816e03d70873d6e554621b9154bc1f Mon Sep 17 00:00:00 2001 From: Dylan DPC <99973273+Dylan-DPC@users.noreply.github.com> Date: Mon, 5 Feb 2024 19:00:36 +0000 Subject: [PATCH 08/20] Update Cargo.toml (#1622) --- tonic/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tonic/Cargo.toml b/tonic/Cargo.toml index 013cc6e72..d03d3115c 100644 --- a/tonic/Cargo.toml +++ b/tonic/Cargo.toml @@ -69,7 +69,7 @@ prost = {version = "0.12", default-features = false, features = ["std"], optiona async-trait = {version = "0.1.13", optional = true} # transport -h2 = {version = "0.3.17", optional = true} +h2 = {version = "0.3.24", optional = true} hyper = {version = "0.14.26", features = ["full"], optional = true} hyper-timeout = {version = "0.4", optional = true} tokio-stream = "0.1" From 23106dd76882a4ffbf2d3ac59c9c403518e429c9 Mon Sep 17 00:00:00 2001 From: tottoto Date: Thu, 8 Feb 2024 23:09:43 +0900 Subject: [PATCH 09/20] chore(tls): Update to rustls 0.22.0 (#1509) * chore(tls): Update to rustls 0.22.0 * chore: Add examples to deny skip-tree --- Cargo.toml | 1 - deny.toml | 1 + tonic/Cargo.toml | 12 +- tonic/src/transport/channel/tls.rs | 4 +- tonic/src/transport/service/connector.rs | 4 +- tonic/src/transport/service/tls.rs | 245 ++++++----------------- 6 files changed, 74 insertions(+), 193 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 117ab4de4..d0db8e6a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,4 +28,3 @@ members = [ "tests/default_stubs", ] resolver = "2" - diff --git a/deny.toml b/deny.toml index 189c307c0..159c135ea 100644 --- a/deny.toml +++ b/deny.toml @@ -22,6 +22,7 @@ skip-tree = [ { name = "syn" }, { name = "bitflags" }, { name = "indexmap" }, + { name = "examples" }, ] [licenses] diff --git a/tonic/Cargo.toml b/tonic/Cargo.toml index d03d3115c..707c42992 100644 --- a/tonic/Cargo.toml +++ b/tonic/Cargo.toml @@ -28,7 +28,7 @@ gzip = ["dep:flate2"] zstd = ["dep:zstd"] default = ["transport", "codegen", "prost"] prost = ["dep:prost"] -tls = ["dep:rustls-pemfile", "transport", "dep:tokio-rustls", "dep:rustls", "tokio/rt", "tokio/macros"] +tls = ["dep:rustls-pki-types", "dep:rustls-pemfile", "transport", "dep:tokio-rustls", "tokio/rt", "tokio/macros"] tls-roots = ["tls-roots-common", "dep:rustls-native-certs"] tls-roots-common = ["tls"] tls-webpki-roots = ["tls-roots-common", "dep:webpki-roots"] @@ -78,11 +78,11 @@ axum = {version = "0.6.9", default_features = false, optional = true} # rustls async-stream = { version = "0.3", optional = true } -rustls-pemfile = { version = "1.0", optional = true } -rustls-native-certs = { version = "0.6.3", optional = true } -tokio-rustls = { version = "0.24.1", optional = true } -rustls = { version = "0.21.7", optional = true } -webpki-roots = { version = "0.25.0", optional = true } +rustls-pki-types = { version = "1.0", optional = true } +rustls-pemfile = { version = "2.0", optional = true } +rustls-native-certs = { version = "0.7", optional = true } +tokio-rustls = { version = "0.25", optional = true } +webpki-roots = { version = "0.26", optional = true } # compression flate2 = {version = "1.0", optional = true} diff --git a/tonic/src/transport/channel/tls.rs b/tonic/src/transport/channel/tls.rs index f3c6f5325..cead6867a 100644 --- a/tonic/src/transport/channel/tls.rs +++ b/tonic/src/transport/channel/tls.rs @@ -60,8 +60,8 @@ impl ClientTlsConfig { pub(crate) fn tls_connector(&self, uri: Uri) -> Result { let domain = match &self.domain { - None => uri.host().ok_or_else(Error::new_invalid_uri)?.to_string(), - Some(domain) => domain.clone(), + Some(domain) => domain, + None => uri.host().ok_or_else(Error::new_invalid_uri)?, }; TlsConnector::new(self.cert.clone(), self.identity.clone(), domain) } diff --git a/tonic/src/transport/service/connector.rs b/tonic/src/transport/service/connector.rs index a5e0d9eb9..6645696ea 100644 --- a/tonic/src/transport/service/connector.rs +++ b/tonic/src/transport/service/connector.rs @@ -39,9 +39,7 @@ impl Connector { _ => return None, }; - host.try_into() - .ok() - .and_then(|dns| TlsConnector::new(None, None, dns).ok()) + TlsConnector::new(None, None, host).ok() } } diff --git a/tonic/src/transport/service/tls.rs b/tonic/src/transport/service/tls.rs index f956132fb..3a6ef62fb 100644 --- a/tonic/src/transport/service/tls.rs +++ b/tonic/src/transport/service/tls.rs @@ -1,17 +1,23 @@ -use super::io::BoxedIo; -use crate::transport::{ - server::{Connected, TlsStream}, - Certificate, Identity, +use std::{ + io::Cursor, + {fmt, sync::Arc}, }; -use std::{fmt, sync::Arc}; + +use rustls_pki_types::{CertificateDer, PrivateKeyDer, ServerName}; use tokio::io::{AsyncRead, AsyncWrite}; use tokio_rustls::{ - rustls::{ClientConfig, RootCertStore, ServerConfig, ServerName}, + rustls::{server::WebPkiClientVerifier, ClientConfig, RootCertStore, ServerConfig}, TlsAcceptor as RustlsAcceptor, TlsConnector as RustlsConnector, }; +use super::io::BoxedIo; +use crate::transport::{ + server::{Connected, TlsStream}, + Certificate, Identity, +}; + /// h2 alpn in plain format for rustls. -const ALPN_H2: &str = "h2"; +const ALPN_H2: &[u8] = b"h2"; #[derive(Debug)] enum TlsError { @@ -23,47 +29,41 @@ enum TlsError { #[derive(Clone)] pub(crate) struct TlsConnector { config: Arc, - domain: Arc, + domain: Arc>, } impl TlsConnector { pub(crate) fn new( ca_cert: Option, identity: Option, - domain: String, + domain: &str, ) -> Result { - let builder = ClientConfig::builder().with_safe_defaults(); + let builder = ClientConfig::builder(); let mut roots = RootCertStore::empty(); #[cfg(feature = "tls-roots")] - roots.add_parsable_certificates(&rustls_native_certs::load_native_certs()?); + roots.add_parsable_certificates(rustls_native_certs::load_native_certs()?); #[cfg(feature = "tls-webpki-roots")] - roots.add_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.iter().map(|ta| { - tokio_rustls::rustls::OwnedTrustAnchor::from_subject_spki_name_constraints( - ta.subject, - ta.spki, - ta.name_constraints, - ) - })); + roots.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); if let Some(cert) = ca_cert { - rustls_keys::add_certs_from_pem(std::io::Cursor::new(cert.as_ref()), &mut roots)?; + add_certs_from_pem(&mut Cursor::new(cert), &mut roots)?; } let builder = builder.with_root_certificates(roots); let mut config = match identity { Some(identity) => { - let (client_cert, client_key) = rustls_keys::load_identity(identity)?; + let (client_cert, client_key) = load_identity(identity)?; builder.with_client_auth_cert(client_cert, client_key)? } None => builder.with_no_client_auth(), }; - config.alpn_protocols.push(ALPN_H2.as_bytes().to_vec()); + config.alpn_protocols.push(ALPN_H2.into()); Ok(Self { config: Arc::new(config), - domain: Arc::new(domain.as_str().try_into()?), + domain: Arc::new(ServerName::try_from(domain)?.to_owned()), }) } @@ -71,22 +71,16 @@ impl TlsConnector { where I: AsyncRead + AsyncWrite + Send + Unpin + 'static, { - let tls_io = { - let io = RustlsConnector::from(self.config.clone()) - .connect(self.domain.as_ref().to_owned(), io) - .await?; + let io = RustlsConnector::from(self.config.clone()) + .connect(self.domain.as_ref().to_owned(), io) + .await?; - let (_, session) = io.get_ref(); - - match session.alpn_protocol() { - Some(b) if b == b"h2" => (), - _ => return Err(TlsError::H2NotNegotiated.into()), - }; - - BoxedIo::new(io) - }; + let (_, session) = io.get_ref(); + if session.alpn_protocol() != Some(ALPN_H2) { + return Err(TlsError::H2NotNegotiated)?; + } - Ok(tls_io) + Ok(BoxedIo::new(io)) } } @@ -107,30 +101,27 @@ impl TlsAcceptor { client_ca_root: Option, client_auth_optional: bool, ) -> Result { - let builder = ServerConfig::builder().with_safe_defaults(); + let builder = ServerConfig::builder(); - let builder = match (client_ca_root, client_auth_optional) { - (None, _) => builder.with_no_client_auth(), - (Some(cert), true) => { - use tokio_rustls::rustls::server::AllowAnyAnonymousOrAuthenticatedClient; - let mut roots = RootCertStore::empty(); - rustls_keys::add_certs_from_pem(std::io::Cursor::new(cert.as_ref()), &mut roots)?; - builder.with_client_cert_verifier( - AllowAnyAnonymousOrAuthenticatedClient::new(roots).boxed(), - ) - } - (Some(cert), false) => { - use tokio_rustls::rustls::server::AllowAnyAuthenticatedClient; + let builder = match client_ca_root { + None => builder.with_no_client_auth(), + Some(cert) => { let mut roots = RootCertStore::empty(); - rustls_keys::add_certs_from_pem(std::io::Cursor::new(cert.as_ref()), &mut roots)?; - builder.with_client_cert_verifier(AllowAnyAuthenticatedClient::new(roots).boxed()) + add_certs_from_pem(&mut Cursor::new(cert), &mut roots)?; + let verifier = if client_auth_optional { + WebPkiClientVerifier::builder(roots.into()).allow_unauthenticated() + } else { + WebPkiClientVerifier::builder(roots.into()) + } + .build()?; + builder.with_client_cert_verifier(verifier) } }; - let (cert, key) = rustls_keys::load_identity(identity)?; + let (cert, key) = load_identity(identity)?; let mut config = builder.with_single_cert(cert, key)?; - config.alpn_protocols.push(ALPN_H2.as_bytes().to_vec()); + config.alpn_protocols.push(ALPN_H2.into()); Ok(Self { inner: Arc::new(config), }) @@ -166,137 +157,29 @@ impl fmt::Display for TlsError { impl std::error::Error for TlsError {} -mod rustls_keys { - use std::io::Cursor; - - use tokio_rustls::rustls::{Certificate, PrivateKey, RootCertStore}; - - use crate::transport::service::tls::TlsError; - use crate::transport::Identity; - - pub(super) fn load_rustls_private_key( - mut cursor: std::io::Cursor<&[u8]>, - ) -> Result { - while let Ok(Some(item)) = rustls_pemfile::read_one(&mut cursor) { - match item { - rustls_pemfile::Item::RSAKey(key) - | rustls_pemfile::Item::PKCS8Key(key) - | rustls_pemfile::Item::ECKey(key) => return Ok(PrivateKey(key)), - _ => continue, - } - } - - // Otherwise we have a Private Key parsing problem - Err(Box::new(TlsError::PrivateKeyParseError)) - } - - pub(crate) fn load_identity( - identity: Identity, - ) -> Result<(Vec, PrivateKey), crate::Error> { - let cert = { - let mut cert = std::io::Cursor::new(identity.cert.as_ref()); - match rustls_pemfile::certs(&mut cert) { - Ok(certs) => certs.into_iter().map(Certificate).collect(), - Err(_) => return Err(Box::new(TlsError::CertificateParseError)), - } - }; - - let key = { - let key = std::io::Cursor::new(identity.key.as_ref()); - match load_rustls_private_key(key) { - Ok(key) => key, - Err(e) => { - return Err(e); - } - } - }; +fn load_identity( + identity: Identity, +) -> Result<(Vec>, PrivateKeyDer<'static>), TlsError> { + let cert = rustls_pemfile::certs(&mut Cursor::new(identity.cert)) + .collect::, _>>() + .map_err(|_| TlsError::CertificateParseError)?; - Ok((cert, key)) - } + let Ok(Some(key)) = rustls_pemfile::private_key(&mut Cursor::new(identity.key)) else { + return Err(TlsError::PrivateKeyParseError); + }; - pub(crate) fn add_certs_from_pem( - mut certs: Cursor<&[u8]>, - roots: &mut RootCertStore, - ) -> Result<(), crate::Error> { - let (_, ignored) = roots.add_parsable_certificates(&rustls_pemfile::certs(&mut certs)?); - match ignored == 0 { - true => Ok(()), - false => Err(Box::new(TlsError::CertificateParseError)), - } - } + Ok((cert, key)) } -#[cfg(test)] -mod tests { - use std::io::Cursor; - - // generated by: openssl ecparam -keygen -name 'prime256v1' - const SIMPLE_EC_KEY: &str = r#"-----BEGIN EC PARAMETERS----- -BggqhkjOPQMBBw== ------END EC PARAMETERS----- ------BEGIN EC PRIVATE KEY----- -MHcCAQEEICIDyh40kMVWGDAYr1gXnMfeMeO3zXYigOaWrg5SNB+zoAoGCCqGSM49 -AwEHoUQDQgAEacJyVg299dkPTzUaMbOmACUfF67yp+ZrDhXVjn/5WxBAgjcmFBHg -Tw8dfwpMzaJPXX5lWYzP276fcmbRO25CXw== ------END EC PRIVATE KEY-----"#; - - // generated by: openssl genpkey -algorithm rsa - const SIMPLE_PKCS8_KEY: &str = r#"-----BEGIN PRIVATE KEY----- -MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAKHkX1YIvqOIAllD -5fKcIxu2kYjIxxAAQrOBRTloGZUKdPFQY1RANB4t/LEaI5/NJ6NK4915pTn35QAQ -zHJl+X4rNFMgVt+o/nY40PgrQxyyv5A0/URp+iS8Yn3GKt3q6p4zguiO9uNXhiiD -b+VKIFRDm4bHR2yM7pNJ0kMdoattAgMBAAECgYAMpw6UaMaNfVnBpD7agT11MwWY -zShRpdOQt++zFuG49kJBgejhcssf+LQhG0vhd2U7q+S3MISrTSaGpMl1v2aKR/nV -G7X4Bb6X8vrVSMrfze2loT0aNri9jKDZkD/muy6+9JkhRa03NOdhDdseokmcqF3L -xsU4BUOOFYb23ycoeQJBANOGxbZu/3BqsPJMQmXWo1CXuaviZ83lTczPtrz9mJVl -Zs/KmGnJ8I2Azu/dlYXsHRvbIbqA93l1M3GnsWl5IxsCQQDD7hKvOY6qzUNyj+R4 -vul/3xaqjiTj59f3jN7Fh6+9AY+WfvEkWfyUUAXY74z43wBgtORfMXnZnjFO96tJ -sswXAkBDYDtb19E/cox4MTg5DfwpMJrwmAYufCqi4Uq4uiI++/SanVKc57jaqbvA -hZkZ9lJzTAJbULcDFgTT3/FPwkkfAkEAqbSDMIzdGuox2n/x9/f8jcpweogmQdUl -xgCZUGSnfkFk2ojXW5Ip6Viqx+0toL6fOCRWjnFvRmPz958kGPCqPwJBAID4y7XV -peOO6Yadu0YbSmFNluRebia6410p5jR21LhG1ty2h22xVhlBWjOC+TyDuKwhmiYT -ed50S3LR1PWt4zE= ------END PRIVATE KEY-----"#; - - // generated by: openssl genrsa - const SIMPLE_RSA_KEY: &str = r#"-----BEGIN RSA PRIVATE KEY----- -MIIEogIBAAKCAQEAoEILGds1/RGBHT7jM4R+EL24sQ6Bsn14GgTHc7WoZ7lainEH -H/n+DtHCYUXYyJnN5AMIi3pkigCP1hdXXBQga3zs3lXoi/mAMkT6vjuqQ7Xg5/95 -ABx5Ztyy25mZNaXm77glyAzSscKHxWYooXVJYG4C3SGuBJJ1zVjxen6Rkzse5Lpr -yZOUUeqeV3M6KbJ/dkR37HFQVwmlctQukFnb4kozFBQDDnkXi9jT/PH00g6JpW3z -YMzdMq2RMadJ0dzYv62OtdtqmQpVz0dRu/yODV4DkhrWwgPRj2uY4DnYthzILESB -x41gxHj+jqo6NW+C+0fr6uh2CXtD0p+ZVANtBQIDAQABAoIBAE7IaOCrLV1dr5WL -BvKancbpHdSdBxGSMOrJkqvFkCZ9ro8EhbYolcb/Q4nCZpInWlpPS3IWFzroj811 -6BJyKoXtAh1DKnE1lNohowrGFiv3S7uBkiCF3wC8Wokud20yQ9dxNdGkzCdrNIfM -cwj8ubfYHTxMhFnnDlaG9R98/V/dFy0FLxL37eMP/heMbcwKKm9P/G2FqvuCn8a4 -FoPbAfvaR64IGCybjoiTjUD7xMHIV4Gr5K07br2TzG2zVlFTacoqXyGBbVVy+ibt -QMh0sn+rMkAy+cFse+yCYZeAFa4FzwGz43sdFviU7uvLG7yXpvZ+uDACFzxlxUVg -v57r1cECgYEA1MMJEe6IunDUyuzRaFNTfQX16QcAv/xLN/1TtVB3HUX5p2bIZKDr -XEl0NCVOrCoz5RsYqbtGmp8B4Yxl3DeX+WeWeD9/f2ZTVGWyBx1N6dZ5hRsyfzG/ -xVBUqYxkChjXQ20cNtf8u7JKdnVjOJen9M92nXhFRTwgH83Id4gPp70CgYEAwNN8 -lvVJnd05ekyf1qIKOSyKiSGnGa5288PpqsjYMZisXy12y4n8cK2pX5Z5PICHeJVu -K99WdTtO7Q4ghCXRB1jR5pTd4/3/3089SQyDnWz9jlA3pGWcSLDTB1dBJXpMQ6yG -cR2dX5hPDNIdKsc+9Bl/OF5PScvGVUYv4SLF6ukCgYAVhh2WyNDgO6XrWYXdzgA2 -N7Im/uReh8F8So57W0aRmZCmFMnVFEp7LZsp41RQKnzRgqo+EYoU/l0MWk27t4wS -WR5pz9KwKsPnV9poydgl/eKRSq0THQ9PgM7v0BoWw2iTk6g1DCivPFw4G6wL/5uo -MozHZXFsjaaaUREktokO6QKBgC3Dg7RILtqaoIOYH+9OseJz4cU+CWyc7XpZKuHv -nO/YbkCAh8syyojrjmEzUz66umwx+t3KubhFBSxZx/nVB9EYkWiKOEdeBxY2tjLa -F3qLXXojK7GGtBrEbLE3UizU47jD/3xlLO59NXWzgFygwR4p1vnH2EWJaV7fs4lZ -OWPRAoGAL0nX0vZ0N9qPETiQan1uHjKYuuFiSP+cwRXVSUYIQM9qDRlKG9zjugwO -az+B6uiR4TrgbwG+faCQwcGk9B8QbcoIb8IigwrWe3XpVaEtcsqFORX0r+tJNDoY -I0O2DOQVPKSK2N5AZzXY4IkybWTV4Yxc7rdXEO3dOOpHGKbpwFQ= ------END RSA PRIVATE KEY-----"#; - - #[test] - fn test_parse_ec_key() { - for (n, key) in [SIMPLE_EC_KEY, SIMPLE_PKCS8_KEY, SIMPLE_RSA_KEY] - .iter() - .enumerate() - { - let c = Cursor::new(key.as_bytes()); - let key = super::rustls_keys::load_rustls_private_key(c); - - assert!(key.is_ok(), "at the {}-th case", n); - } +fn add_certs_from_pem( + mut certs: &mut dyn std::io::BufRead, + roots: &mut RootCertStore, +) -> Result<(), crate::Error> { + for cert in rustls_pemfile::certs(&mut certs).collect::, _>>()? { + roots + .add(cert) + .map_err(|_| TlsError::CertificateParseError)?; } + + Ok(()) } From 4263f87503010497147b50c9a43ff85ee608c830 Mon Sep 17 00:00:00 2001 From: tottoto Date: Thu, 8 Feb 2024 23:15:28 +0900 Subject: [PATCH 10/20] feat(tls): Implement AsMut for Certificate (#1621) --- tonic/src/transport/tls.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tonic/src/transport/tls.rs b/tonic/src/transport/tls.rs index 1dd4c06a4..c2b7ef23f 100644 --- a/tonic/src/transport/tls.rs +++ b/tonic/src/transport/tls.rs @@ -42,6 +42,12 @@ impl AsRef<[u8]> for Certificate { } } +impl AsMut<[u8]> for Certificate { + fn as_mut(&mut self) -> &mut [u8] { + self.pem.as_mut() + } +} + impl Identity { /// Parse a PEM encoded certificate and private key. /// From 209fcbe5892c4c81fea060676a347ab79a60ab29 Mon Sep 17 00:00:00 2001 From: Lucio Franco Date: Thu, 8 Feb 2024 10:04:21 -0500 Subject: [PATCH 11/20] chore(ci): fix udeps warning errors (#1627) --- tonic-types/src/richer_error/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tonic-types/src/richer_error/mod.rs b/tonic-types/src/richer_error/mod.rs index 411887ce5..927bbc97d 100644 --- a/tonic-types/src/richer_error/mod.rs +++ b/tonic-types/src/richer_error/mod.rs @@ -21,6 +21,7 @@ trait IntoAny { fn into_any(self) -> Any; } +#[allow(dead_code)] trait FromAny { fn from_any(any: Any) -> Result where From ea8cd3f384e953e177f20a62aa156a75676853f4 Mon Sep 17 00:00:00 2001 From: Lucio Franco Date: Thu, 8 Feb 2024 10:28:04 -0500 Subject: [PATCH 12/20] chore: prepare v0.11.0 release (#1626) --- CHANGELOG.md | 16 ++++++++++++++++ examples/helloworld-tutorial.md | 4 ++-- examples/routeguide-tutorial.md | 4 ++-- tonic-build/Cargo.toml | 4 ++-- tonic-build/src/lib.rs | 2 +- tonic-health/Cargo.toml | 6 +++--- tonic-health/src/lib.rs | 2 +- tonic-reflection/Cargo.toml | 8 ++++---- tonic-reflection/src/lib.rs | 2 +- tonic-types/Cargo.toml | 6 +++--- tonic-types/src/lib.rs | 2 +- tonic-web/Cargo.toml | 6 +++--- tonic-web/src/lib.rs | 2 +- tonic/Cargo.toml | 6 +++--- tonic/src/lib.rs | 2 +- 15 files changed, 44 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07674643f..794fb35de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +# [](https://github.com/hyperium/tonic/compare/v0.10.2...v0.11.0) (2024-02-08) + +BREAKING CHANGES: + +- Removed `NamedService` from the `transport` module, please import it via + `tonic::server::NamedService`. +- MSRV bumped to `1.70`. + +### Features + +- Added `zstd` compression support. +- Added connection timeout for `connecto_with_connector_lazy`. +- Upgrade rustls to `v0.22` +- Feature gate server implementation for `tonic-reflection`. + + # [0.10.2](https://github.com/hyperium/tonic/compare/v0.10.1...v0.10.2) (2023-09-28) diff --git a/examples/helloworld-tutorial.md b/examples/helloworld-tutorial.md index 8c494db5a..b2d6e65fc 100644 --- a/examples/helloworld-tutorial.md +++ b/examples/helloworld-tutorial.md @@ -112,12 +112,12 @@ name = "helloworld-client" path = "src/client.rs" [dependencies] -tonic = "0.10" +tonic = "0.11" prost = "0.12" tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } [build-dependencies] -tonic-build = "0.10" +tonic-build = "0.11" ``` We include `tonic-build` as a useful way to incorporate the generation of our client and server gRPC code into the build process of our application. We will setup this build process now: diff --git a/examples/routeguide-tutorial.md b/examples/routeguide-tutorial.md index 0d8b2069f..88ae833af 100644 --- a/examples/routeguide-tutorial.md +++ b/examples/routeguide-tutorial.md @@ -174,7 +174,7 @@ Edit `Cargo.toml` and add all the dependencies we'll need for this example: ```toml [dependencies] -tonic = "0.10" +tonic = "0.11" prost = "0.12" tokio = { version = "1.0", features = ["rt-multi-thread", "macros", "sync", "time"] } tokio-stream = "0.1" @@ -185,7 +185,7 @@ serde_json = "1.0" rand = "0.7" [build-dependencies] -tonic-build = "0.10" +tonic-build = "0.11" ``` Create a `build.rs` file at the root of your crate: diff --git a/tonic-build/Cargo.toml b/tonic-build/Cargo.toml index b33d8f13e..b24db759e 100644 --- a/tonic-build/Cargo.toml +++ b/tonic-build/Cargo.toml @@ -4,7 +4,7 @@ categories = ["network-programming", "asynchronous"] description = """ Codegen module of `tonic` gRPC implementation. """ -documentation = "https://docs.rs/tonic-build/0.10.2" +documentation = "https://docs.rs/tonic-build/0.11.0" edition = "2021" homepage = "https://github.com/hyperium/tonic" keywords = ["rpc", "grpc", "async", "codegen", "protobuf"] @@ -12,7 +12,7 @@ license = "MIT" name = "tonic-build" readme = "README.md" repository = "https://github.com/hyperium/tonic" -version = "0.10.2" +version = "0.11.0" [dependencies] prettyplease = { version = "0.2" } diff --git a/tonic-build/src/lib.rs b/tonic-build/src/lib.rs index ddd739c62..015796508 100644 --- a/tonic-build/src/lib.rs +++ b/tonic-build/src/lib.rs @@ -70,7 +70,7 @@ html_logo_url = "https://raw.githubusercontent.com/tokio-rs/website/master/public/img/icons/tonic.svg" )] #![deny(rustdoc::broken_intra_doc_links)] -#![doc(html_root_url = "https://docs.rs/tonic-build/0.10.2")] +#![doc(html_root_url = "https://docs.rs/tonic-build/0.11.0")] #![doc(issue_tracker_base_url = "https://github.com/hyperium/tonic/issues/")] #![doc(test(no_crate_inject, attr(deny(rust_2018_idioms))))] #![cfg_attr(docsrs, feature(doc_cfg))] diff --git a/tonic-health/Cargo.toml b/tonic-health/Cargo.toml index d07286144..f26472faf 100644 --- a/tonic-health/Cargo.toml +++ b/tonic-health/Cargo.toml @@ -4,7 +4,7 @@ categories = ["network-programming", "asynchronous"] description = """ Health Checking module of `tonic` gRPC implementation. """ -documentation = "https://docs.rs/tonic-health/0.10.2" +documentation = "https://docs.rs/tonic-health/0.11.0" edition = "2021" homepage = "https://github.com/hyperium/tonic" keywords = ["rpc", "grpc", "async", "healthcheck"] @@ -12,7 +12,7 @@ license = "MIT" name = "tonic-health" readme = "README.md" repository = "https://github.com/hyperium/tonic" -version = "0.10.2" +version = "0.11.0" [features] default = ["transport"] @@ -23,7 +23,7 @@ async-stream = "0.3" prost = "0.12" tokio = {version = "1.0", features = ["sync"]} tokio-stream = "0.1" -tonic = { version = "0.10", path = "../tonic", default-features = false, features = ["codegen", "prost"] } +tonic = { version = "0.11", path = "../tonic", default-features = false, features = ["codegen", "prost"] } [dev-dependencies] tokio = {version = "1.0", features = ["rt-multi-thread", "macros"]} diff --git a/tonic-health/src/lib.rs b/tonic-health/src/lib.rs index 82fdaca16..1a4ce57cb 100644 --- a/tonic-health/src/lib.rs +++ b/tonic-health/src/lib.rs @@ -16,7 +16,7 @@ html_logo_url = "https://raw.githubusercontent.com/tokio-rs/website/master/public/img/icons/tonic.svg" )] #![deny(rustdoc::broken_intra_doc_links)] -#![doc(html_root_url = "https://docs.rs/tonic-health/0.10.2")] +#![doc(html_root_url = "https://docs.rs/tonic-health/0.11.0")] #![doc(issue_tracker_base_url = "https://github.com/hyperium/tonic/issues/")] #![doc(test(no_crate_inject, attr(deny(rust_2018_idioms))))] #![cfg_attr(docsrs, feature(doc_cfg))] diff --git a/tonic-reflection/Cargo.toml b/tonic-reflection/Cargo.toml index e411fad5d..706b735ff 100644 --- a/tonic-reflection/Cargo.toml +++ b/tonic-reflection/Cargo.toml @@ -9,13 +9,13 @@ Server Reflection module of `tonic` gRPC implementation. """ edition = "2021" homepage = "https://github.com/hyperium/tonic" -documentation = "https://docs.rs/tonic-reflection/0.10.2" +documentation = "https://docs.rs/tonic-reflection/0.11.0" keywords = ["rpc", "grpc", "async", "reflection"] license = "MIT" name = "tonic-reflection" readme = "README.md" repository = "https://github.com/hyperium/tonic" -version = "0.10.2" +version = "0.11.0" [package.metadata.docs.rs] all-features = true @@ -30,7 +30,7 @@ prost = "0.12" prost-types = {version = "0.12", optional = true} tokio = { version = "1.0", features = ["sync", "rt"], optional = true } tokio-stream = {version = "0.1", features = ["net"], optional = true } -tonic = { version = "0.10", path = "../tonic", default-features = false, features = ["codegen", "prost"] } +tonic = { version = "0.11", path = "../tonic", default-features = false, features = ["codegen", "prost"] } [dev-dependencies] -tonic = { version = "0.10", path = "../tonic", default-features = false, features = ["transport"] } +tonic = { version = "0.11", path = "../tonic", default-features = false, features = ["transport"] } diff --git a/tonic-reflection/src/lib.rs b/tonic-reflection/src/lib.rs index c15f23229..c5e0d92d0 100644 --- a/tonic-reflection/src/lib.rs +++ b/tonic-reflection/src/lib.rs @@ -10,7 +10,7 @@ html_logo_url = "https://github.com/hyperium/tonic/raw/master/.github/assets/tonic-docs.png" )] #![deny(rustdoc::broken_intra_doc_links)] -#![doc(html_root_url = "https://docs.rs/tonic-reflection/0.10.2")] +#![doc(html_root_url = "https://docs.rs/tonic-reflection/0.11.0")] #![doc(issue_tracker_base_url = "https://github.com/hyperium/tonic/issues/")] #![doc(test(no_crate_inject, attr(deny(rust_2018_idioms))))] #![cfg_attr(docsrs, feature(doc_cfg))] diff --git a/tonic-types/Cargo.toml b/tonic-types/Cargo.toml index a0fd7f89a..0e13a66ec 100644 --- a/tonic-types/Cargo.toml +++ b/tonic-types/Cargo.toml @@ -7,7 +7,7 @@ categories = ["web-programming", "network-programming", "asynchronous"] description = """ A collection of useful protobuf types that can be used with `tonic`. """ -documentation = "https://docs.rs/tonic-types/0.10.2" +documentation = "https://docs.rs/tonic-types/0.11.0" edition = "2021" homepage = "https://github.com/hyperium/tonic" keywords = ["rpc", "grpc", "protobuf"] @@ -15,9 +15,9 @@ license = "MIT" name = "tonic-types" readme = "README.md" repository = "https://github.com/hyperium/tonic" -version = "0.10.2" +version = "0.11.0" [dependencies] prost = "0.12" prost-types = "0.12" -tonic = {version = "0.10", path = "../tonic", default-features = false} +tonic = {version = "0.11", path = "../tonic", default-features = false} diff --git a/tonic-types/src/lib.rs b/tonic-types/src/lib.rs index 4e81cd287..fe2886a60 100644 --- a/tonic-types/src/lib.rs +++ b/tonic-types/src/lib.rs @@ -150,7 +150,7 @@ html_logo_url = "https://raw.githubusercontent.com/tokio-rs/website/master/public/img/icons/tonic.svg" )] #![deny(rustdoc::broken_intra_doc_links)] -#![doc(html_root_url = "https://docs.rs/tonic-types/0.10.2")] +#![doc(html_root_url = "https://docs.rs/tonic-types/0.11.0")] #![doc(issue_tracker_base_url = "https://github.com/hyperium/tonic/issues/")] mod generated { diff --git a/tonic-web/Cargo.toml b/tonic-web/Cargo.toml index 16d8909b1..501fe26fb 100644 --- a/tonic-web/Cargo.toml +++ b/tonic-web/Cargo.toml @@ -4,7 +4,7 @@ categories = ["network-programming", "asynchronous"] description = """ grpc-web protocol translation for tonic services. """ -documentation = "https://docs.rs/tonic-web/0.10.2" +documentation = "https://docs.rs/tonic-web/0.11.0" edition = "2021" homepage = "https://github.com/hyperium/tonic" keywords = ["rpc", "grpc", "grpc-web"] @@ -12,7 +12,7 @@ license = "MIT" name = "tonic-web" readme = "README.md" repository = "https://github.com/hyperium/tonic" -version = "0.10.2" +version = "0.11.0" [dependencies] base64 = "0.21" @@ -22,7 +22,7 @@ http = "0.2" http-body = "0.4" hyper = {version = "0.14", default-features = false, features = ["stream"]} pin-project = "1" -tonic = {version = "0.10", path = "../tonic", default-features = false} +tonic = {version = "0.11", path = "../tonic", default-features = false} tower-service = "0.3" tower-layer = "0.3" tower-http = { version = "0.4", features = ["cors"] } diff --git a/tonic-web/src/lib.rs b/tonic-web/src/lib.rs index cc11ed56b..16e57e19d 100644 --- a/tonic-web/src/lib.rs +++ b/tonic-web/src/lib.rs @@ -94,7 +94,7 @@ rust_2018_idioms, unreachable_pub )] -#![doc(html_root_url = "https://docs.rs/tonic-web/0.10.2")] +#![doc(html_root_url = "https://docs.rs/tonic-web/0.11.0")] #![doc(issue_tracker_base_url = "https://github.com/hyperium/tonic/issues/")] pub use call::GrpcWebCall; diff --git a/tonic/Cargo.toml b/tonic/Cargo.toml index 707c42992..ff029f042 100644 --- a/tonic/Cargo.toml +++ b/tonic/Cargo.toml @@ -7,20 +7,20 @@ name = "tonic" # - Cargo.toml # - README.md # - Update CHANGELOG.md. -# - Create "v0.10.x" git tag. +# - Create "v0.11.x" git tag. authors = ["Lucio Franco "] categories = ["web-programming", "network-programming", "asynchronous"] description = """ A gRPC over HTTP/2 implementation focused on high performance, interoperability, and flexibility. """ -documentation = "https://docs.rs/tonic/0.10.2" +documentation = "https://docs.rs/tonic/0.11.0" edition = "2021" homepage = "https://github.com/hyperium/tonic" keywords = ["rpc", "grpc", "async", "futures", "protobuf"] license = "MIT" readme = "../README.md" repository = "https://github.com/hyperium/tonic" -version = "0.10.2" +version = "0.11.0" [features] codegen = ["dep:async-trait"] diff --git a/tonic/src/lib.rs b/tonic/src/lib.rs index 8aa80a121..325c6af47 100644 --- a/tonic/src/lib.rs +++ b/tonic/src/lib.rs @@ -89,7 +89,7 @@ #![doc( html_logo_url = "https://raw.githubusercontent.com/tokio-rs/website/master/public/img/icons/tonic.svg" )] -#![doc(html_root_url = "https://docs.rs/tonic/0.10.2")] +#![doc(html_root_url = "https://docs.rs/tonic/0.11.0")] #![doc(issue_tracker_base_url = "https://github.com/hyperium/tonic/issues/")] #![doc(test(no_crate_inject, attr(deny(rust_2018_idioms))))] #![cfg_attr(docsrs, feature(doc_cfg))] From e31f5ccfc339e9f3d82a020e28751f188c81c564 Mon Sep 17 00:00:00 2001 From: tottoto Date: Fri, 9 Feb 2024 06:18:14 +0900 Subject: [PATCH 13/20] chore: Replace tokio::pin with std::pin::pin (#1593) --- tonic/Cargo.toml | 7 +++---- tonic/src/client/grpc.rs | 4 ++-- tonic/src/codec/prost.rs | 19 +++++++------------ tonic/src/server/grpc.rs | 8 +++----- tonic/src/transport/server/incoming.rs | 6 +++--- 5 files changed, 18 insertions(+), 26 deletions(-) diff --git a/tonic/Cargo.toml b/tonic/Cargo.toml index ff029f042..99388e357 100644 --- a/tonic/Cargo.toml +++ b/tonic/Cargo.toml @@ -28,7 +28,7 @@ gzip = ["dep:flate2"] zstd = ["dep:zstd"] default = ["transport", "codegen", "prost"] prost = ["dep:prost"] -tls = ["dep:rustls-pki-types", "dep:rustls-pemfile", "transport", "dep:tokio-rustls", "tokio/rt", "tokio/macros"] +tls = ["dep:rustls-pki-types", "dep:rustls-pemfile", "transport", "dep:tokio-rustls", "dep:tokio", "tokio?/rt", "tokio?/macros"] tls-roots = ["tls-roots-common", "dep:rustls-native-certs"] tls-roots-common = ["tls"] tls-webpki-roots = ["tls-roots-common", "dep:webpki-roots"] @@ -38,8 +38,7 @@ transport = [ "channel", "dep:h2", "dep:hyper", - "tokio/net", - "tokio/time", + "dep:tokio", "tokio?/net", "tokio?/time", "dep:tower", "dep:hyper-timeout", ] @@ -55,7 +54,6 @@ bytes = "1.0" http = "0.2" tracing = "0.1" -tokio = "1.0.1" http-body = "0.4.4" percent-encoding = "2.1" pin-project = "1.0.11" @@ -72,6 +70,7 @@ async-trait = {version = "0.1.13", optional = true} h2 = {version = "0.3.24", optional = true} hyper = {version = "0.14.26", features = ["full"], optional = true} hyper-timeout = {version = "0.4", optional = true} +tokio = {version = "1.0.1", optional = true} tokio-stream = "0.1" tower = {version = "0.4.7", default-features = false, features = ["balance", "buffer", "discover", "limit", "load", "make", "timeout", "util"], optional = true} axum = {version = "0.6.9", default_features = false, optional = true} diff --git a/tonic/src/client/grpc.rs b/tonic/src/client/grpc.rs index e070f08d3..b02ebb949 100644 --- a/tonic/src/client/grpc.rs +++ b/tonic/src/client/grpc.rs @@ -11,7 +11,7 @@ use http::{ uri::{PathAndQuery, Uri}, }; use http_body::Body; -use std::{fmt, future}; +use std::{fmt, future, pin::pin}; use tokio_stream::{Stream, StreamExt}; /// A gRPC client dispatcher. @@ -239,7 +239,7 @@ impl Grpc { let (mut parts, body, extensions) = self.streaming(request, path, codec).await?.into_parts(); - tokio::pin!(body); + let mut body = pin!(body); let message = body .try_next() diff --git a/tonic/src/codec/prost.rs b/tonic/src/codec/prost.rs index d2f1652f4..aa872a9ba 100644 --- a/tonic/src/codec/prost.rs +++ b/tonic/src/codec/prost.rs @@ -84,6 +84,7 @@ mod tests { use crate::{Code, Status}; use bytes::{Buf, BufMut, BytesMut}; use http_body::Body; + use std::pin::pin; const LEN: usize = 10000; // The maximum uncompressed size in bytes for a message. Set to 2MB. @@ -157,15 +158,13 @@ mod tests { let messages = std::iter::repeat_with(move || Ok::<_, Status>(msg.clone())).take(10000); let source = tokio_stream::iter(messages); - let body = encode_server( + let mut body = pin!(encode_server( encoder, source, None, SingleMessageCompressionOverride::default(), None, - ); - - tokio::pin!(body); + )); while let Some(r) = body.data().await { r.unwrap(); @@ -181,15 +180,13 @@ mod tests { let messages = std::iter::once(Ok::<_, Status>(msg)); let source = tokio_stream::iter(messages); - let body = encode_server( + let mut body = pin!(encode_server( encoder, source, None, SingleMessageCompressionOverride::default(), Some(MAX_MESSAGE_SIZE), - ); - - tokio::pin!(body); + )); assert!(body.data().await.is_none()); assert_eq!( @@ -215,15 +212,13 @@ mod tests { let messages = std::iter::once(Ok::<_, Status>(msg)); let source = tokio_stream::iter(messages); - let body = encode_server( + let mut body = pin!(encode_server( encoder, source, None, SingleMessageCompressionOverride::default(), Some(usize::MAX), - ); - - tokio::pin!(body); + )); assert!(body.data().await.is_none()); assert_eq!( diff --git a/tonic/src/server/grpc.rs b/tonic/src/server/grpc.rs index ec94b97fb..5330b30ed 100644 --- a/tonic/src/server/grpc.rs +++ b/tonic/src/server/grpc.rs @@ -8,7 +8,7 @@ use crate::{ Code, Request, Status, }; use http_body::Body; -use std::fmt; +use std::{fmt, pin::pin}; use tokio_stream::{Stream, StreamExt}; macro_rules! t { @@ -375,14 +375,12 @@ where let (parts, body) = request.into_parts(); - let stream = Streaming::new_request( + let mut stream = pin!(Streaming::new_request( self.codec.decoder(), body, request_compression_encoding, self.max_decoding_message_size, - ); - - tokio::pin!(stream); + )); let message = stream .try_next() diff --git a/tonic/src/transport/server/incoming.rs b/tonic/src/transport/server/incoming.rs index 61aadc93d..bc1bb7650 100644 --- a/tonic/src/transport/server/incoming.rs +++ b/tonic/src/transport/server/incoming.rs @@ -6,7 +6,7 @@ use hyper::server::{ }; use std::{ net::SocketAddr, - pin::Pin, + pin::{pin, Pin}, task::{Context, Poll}, time::Duration, }; @@ -26,7 +26,7 @@ where IE: Into, { async_stream::try_stream! { - tokio::pin!(incoming); + let mut incoming = pin!(incoming); while let Some(item) = incoming.next().await { yield item.map(ServerIo::new_io)? @@ -44,7 +44,7 @@ where IE: Into, { async_stream::try_stream! { - tokio::pin!(incoming); + let mut incoming = pin!(incoming); let mut tasks = tokio::task::JoinSet::new(); From 4b04d0e1d9e59853befa87fcd253067267379907 Mon Sep 17 00:00:00 2001 From: Markus Legner Date: Mon, 12 Feb 2024 17:03:50 +0100 Subject: [PATCH 14/20] chore: fix 0.11.0 changelog entry (#1633) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 794fb35de..422f34d03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# [](https://github.com/hyperium/tonic/compare/v0.10.2...v0.11.0) (2024-02-08) +# [0.11.0](https://github.com/hyperium/tonic/compare/v0.10.2...v0.11.0) (2024-02-08) BREAKING CHANGES: From 408f46d5f2e1a25547831eb4b064bdeaf3868979 Mon Sep 17 00:00:00 2001 From: tottoto Date: Tue, 13 Feb 2024 01:41:52 +0900 Subject: [PATCH 15/20] chore: Use nightly-2024-02-06 on udeps ci (#1628) Co-authored-by: Lucio Franco --- .github/workflows/CI.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 06bf43629..52cd882b2 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -52,7 +52,9 @@ jobs: option: --exclude-features uds steps: - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@nightly + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly-2024-02-06 - uses: taiki-e/install-action@cargo-hack - uses: taiki-e/install-action@cargo-udeps - name: Install protoc From 18a2b30922460be02829706cf9dd0cd1ec6a19c1 Mon Sep 17 00:00:00 2001 From: Kenny <86278669+kvcache@users.noreply.github.com> Date: Tue, 20 Feb 2024 09:38:13 -0800 Subject: [PATCH 16/20] feat(build): Custom codecs for generated code (#1599) * feat(tonic): Custom codecs for generated code Broadly, this change does 2 things: 1. Allow the built-in Prost codec to have its buffer sizes customized 2. Allow users to specify custom codecs on the tonic_build::prost::Builder The Prost codec is convenient, and handles any normal use case. However, the buffer sizes today are too large in some cases - and they may grow too aggressively. By exposing BufferSettings, users can make a small custom codec with their own BufferSettings to control their memory usage - or give enormous buffers to rpc's, as their use case requires. While one can define a custom service and methods with a custom codec today explicitly in Rust, the code generator does not have a means to supply a custom codec. I've reached for .codec... on the tonic_build::prost::Builder many times and keep forgetting it's not there. This change adds .codec_path to the Builder, so people can simply add their custom buffer codec or even their own full top level codec without reaching for manual service definition. * replace threadlocal with service wrapper * pull back ProstEn/Decoder, clean up other comments * clippy and fmt * feedback, clean up straggler changes --- examples/Cargo.toml | 8 +++ examples/build.rs | 8 +++ examples/src/codec_buffers/client.rs | 30 ++++++++ examples/src/codec_buffers/common.rs | 41 +++++++++++ examples/src/codec_buffers/server.rs | 51 +++++++++++++ tonic-build/src/compile_settings.rs | 14 ++++ tonic-build/src/lib.rs | 2 + tonic-build/src/prost.rs | 103 +++++++++++++++++++++------ tonic/src/codec/compression.rs | 25 +++++-- tonic/src/codec/decode.rs | 27 ++++--- tonic/src/codec/encode.rs | 31 +++++--- tonic/src/codec/mod.rs | 79 ++++++++++++++++++++ tonic/src/codec/prost.rs | 92 ++++++++++++++++++++++-- 13 files changed, 458 insertions(+), 53 deletions(-) create mode 100644 examples/src/codec_buffers/client.rs create mode 100644 examples/src/codec_buffers/common.rs create mode 100644 examples/src/codec_buffers/server.rs create mode 100644 tonic-build/src/compile_settings.rs diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 5c3e7e8b5..2239336e1 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -276,6 +276,14 @@ required-features = ["cancellation"] name = "cancellation-client" path = "src/cancellation/client.rs" +[[bin]] +name = "codec-buffers-server" +path = "src/codec_buffers/server.rs" + +[[bin]] +name = "codec-buffers-client" +path = "src/codec_buffers/client.rs" + [features] gcp = ["dep:prost-types", "tonic/tls"] diff --git a/examples/build.rs b/examples/build.rs index 892b0d96c..454a77214 100644 --- a/examples/build.rs +++ b/examples/build.rs @@ -33,6 +33,14 @@ fn main() { .unwrap(); build_json_codec_service(); + + let smallbuff_copy = out_dir.join("smallbuf"); + let _ = std::fs::create_dir(smallbuff_copy.clone()); // This will panic below if the directory failed to create + tonic_build::configure() + .out_dir(smallbuff_copy) + .codec_path("crate::common::SmallBufferCodec") + .compile(&["proto/helloworld/helloworld.proto"], &["proto"]) + .unwrap(); } // Manually define the json.helloworld.Greeter service which used a custom JsonCodec to use json diff --git a/examples/src/codec_buffers/client.rs b/examples/src/codec_buffers/client.rs new file mode 100644 index 000000000..267e19dbf --- /dev/null +++ b/examples/src/codec_buffers/client.rs @@ -0,0 +1,30 @@ +//! A HelloWorld example that uses a custom codec instead of the default Prost codec. +//! +//! Generated code is the output of codegen as defined in the `examples/build.rs` file. +//! The generation is the one with .codec_path("crate::common::SmallBufferCodec") +//! The generated code assumes that a module `crate::common` exists which defines +//! `SmallBufferCodec`, and `SmallBufferCodec` must have a Default implementation. + +pub mod common; + +pub mod small_buf { + include!(concat!(env!("OUT_DIR"), "/smallbuf/helloworld.rs")); +} +use small_buf::greeter_client::GreeterClient; + +use crate::small_buf::HelloRequest; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let mut client = GreeterClient::connect("http://[::1]:50051").await?; + + let request = tonic::Request::new(HelloRequest { + name: "Tonic".into(), + }); + + let response = client.say_hello(request).await?; + + println!("RESPONSE={:?}", response); + + Ok(()) +} diff --git a/examples/src/codec_buffers/common.rs b/examples/src/codec_buffers/common.rs new file mode 100644 index 000000000..c8f9ed777 --- /dev/null +++ b/examples/src/codec_buffers/common.rs @@ -0,0 +1,41 @@ +//! This module defines a common encoder with small buffers. This is useful +//! when you have many concurrent RPC's, and not a huge volume of data per +//! rpc normally. +//! +//! Note that you can customize your codecs per call to the code generator's +//! compile function. This lets you group services by their codec needs. +//! +//! While this codec demonstrates customizing the built-in Prost codec, you +//! can use this to implement other codecs as well, as long as they have a +//! Default implementation. + +use std::marker::PhantomData; + +use prost::Message; +use tonic::codec::{BufferSettings, Codec, ProstCodec}; + +#[derive(Debug, Clone, Copy, Default)] +pub struct SmallBufferCodec(PhantomData<(T, U)>); + +impl Codec for SmallBufferCodec +where + T: Message + Send + 'static, + U: Message + Default + Send + 'static, +{ + type Encode = T; + type Decode = U; + + type Encoder = as Codec>::Encoder; + type Decoder = as Codec>::Decoder; + + fn encoder(&mut self) -> Self::Encoder { + // Here, we will just customize the prost codec's internal buffer settings. + // You can of course implement a complete Codec, Encoder, and Decoder if + // you wish! + ProstCodec::::raw_encoder(BufferSettings::new(512, 4096)) + } + + fn decoder(&mut self) -> Self::Decoder { + ProstCodec::::raw_decoder(BufferSettings::new(512, 4096)) + } +} diff --git a/examples/src/codec_buffers/server.rs b/examples/src/codec_buffers/server.rs new file mode 100644 index 000000000..b30c797d3 --- /dev/null +++ b/examples/src/codec_buffers/server.rs @@ -0,0 +1,51 @@ +//! A HelloWorld example that uses a custom codec instead of the default Prost codec. +//! +//! Generated code is the output of codegen as defined in the `examples/build.rs` file. +//! The generation is the one with .codec_path("crate::common::SmallBufferCodec") +//! The generated code assumes that a module `crate::common` exists which defines +//! `SmallBufferCodec`, and `SmallBufferCodec` must have a Default implementation. + +use tonic::{transport::Server, Request, Response, Status}; + +pub mod common; + +pub mod small_buf { + include!(concat!(env!("OUT_DIR"), "/smallbuf/helloworld.rs")); +} +use small_buf::{ + greeter_server::{Greeter, GreeterServer}, + HelloReply, HelloRequest, +}; + +#[derive(Default)] +pub struct MyGreeter {} + +#[tonic::async_trait] +impl Greeter for MyGreeter { + async fn say_hello( + &self, + request: Request, + ) -> Result, Status> { + println!("Got a request from {:?}", request.remote_addr()); + + let reply = HelloReply { + message: format!("Hello {}!", request.into_inner().name), + }; + Ok(Response::new(reply)) + } +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let addr = "[::1]:50051".parse().unwrap(); + let greeter = MyGreeter::default(); + + println!("GreeterServer listening on {}", addr); + + Server::builder() + .add_service(GreeterServer::new(greeter)) + .serve(addr) + .await?; + + Ok(()) +} diff --git a/tonic-build/src/compile_settings.rs b/tonic-build/src/compile_settings.rs new file mode 100644 index 000000000..e2de97910 --- /dev/null +++ b/tonic-build/src/compile_settings.rs @@ -0,0 +1,14 @@ +#[derive(Debug, Clone)] +pub(crate) struct CompileSettings { + #[cfg(feature = "prost")] + pub(crate) codec_path: String, +} + +impl Default for CompileSettings { + fn default() -> Self { + Self { + #[cfg(feature = "prost")] + codec_path: "tonic::codec::ProstCodec".to_string(), + } + } +} diff --git a/tonic-build/src/lib.rs b/tonic-build/src/lib.rs index 015796508..afc9e0ced 100644 --- a/tonic-build/src/lib.rs +++ b/tonic-build/src/lib.rs @@ -97,6 +97,8 @@ pub mod server; mod code_gen; pub use code_gen::CodeGenBuilder; +mod compile_settings; + /// Service generation trait. /// /// This trait can be implemented and consumed diff --git a/tonic-build/src/prost.rs b/tonic-build/src/prost.rs index dafbe9814..1c76e103b 100644 --- a/tonic-build/src/prost.rs +++ b/tonic-build/src/prost.rs @@ -1,4 +1,4 @@ -use crate::code_gen::CodeGenBuilder; +use crate::{code_gen::CodeGenBuilder, compile_settings::CompileSettings}; use super::Attributes; use proc_macro2::TokenStream; @@ -41,6 +41,7 @@ pub fn configure() -> Builder { disable_comments: HashSet::default(), use_arc_self: false, generate_default_stubs: false, + compile_settings: CompileSettings::default(), } } @@ -61,61 +62,98 @@ pub fn compile_protos(proto: impl AsRef) -> io::Result<()> { Ok(()) } -const PROST_CODEC_PATH: &str = "tonic::codec::ProstCodec"; - /// Non-path Rust types allowed for request/response types. const NON_PATH_TYPE_ALLOWLIST: &[&str] = &["()"]; -impl crate::Service for Service { - type Method = Method; +/// Newtype wrapper for prost to add tonic-specific extensions +struct TonicBuildService { + prost_service: Service, + methods: Vec, +} + +impl TonicBuildService { + fn new(prost_service: Service, settings: CompileSettings) -> Self { + Self { + // CompileSettings are currently only consumed method-by-method but if you need them in the Service, here's your spot. + // The tonic_build::Service trait specifies that methods are borrowed, so they have to reified up front. + methods: prost_service + .methods + .iter() + .map(|prost_method| TonicBuildMethod { + prost_method: prost_method.clone(), + settings: settings.clone(), + }) + .collect(), + prost_service, + } + } +} + +/// Newtype wrapper for prost to add tonic-specific extensions +struct TonicBuildMethod { + prost_method: Method, + settings: CompileSettings, +} + +impl crate::Service for TonicBuildService { + type Method = TonicBuildMethod; type Comment = String; fn name(&self) -> &str { - &self.name + &self.prost_service.name } fn package(&self) -> &str { - &self.package + &self.prost_service.package } fn identifier(&self) -> &str { - &self.proto_name + &self.prost_service.proto_name } fn comment(&self) -> &[Self::Comment] { - &self.comments.leading[..] + &self.prost_service.comments.leading[..] } fn methods(&self) -> &[Self::Method] { - &self.methods[..] + &self.methods } } -impl crate::Method for Method { +impl crate::Method for TonicBuildMethod { type Comment = String; fn name(&self) -> &str { - &self.name + &self.prost_method.name } fn identifier(&self) -> &str { - &self.proto_name + &self.prost_method.proto_name } + /// For code generation, you can override the codec. + /// + /// You should set the codec path to an import path that has a free + /// function like `fn default()`. The default value is tonic::codec::ProstCodec, + /// which returns a default-configured ProstCodec. You may wish to configure + /// the codec, e.g., with a buffer configuration. + /// + /// Though ProstCodec implements Default, it is currently only required that + /// the function match the Default trait's function spec. fn codec_path(&self) -> &str { - PROST_CODEC_PATH + &self.settings.codec_path } fn client_streaming(&self) -> bool { - self.client_streaming + self.prost_method.client_streaming } fn server_streaming(&self) -> bool { - self.server_streaming + self.prost_method.server_streaming } fn comment(&self) -> &[Self::Comment] { - &self.comments.leading[..] + &self.prost_method.comments.leading[..] } fn request_response_name( @@ -140,8 +178,14 @@ impl crate::Method for Method { } }; - let request = convert_type(&self.input_proto_type, &self.input_type); - let response = convert_type(&self.output_proto_type, &self.output_type); + let request = convert_type( + &self.prost_method.input_proto_type, + &self.prost_method.input_type, + ); + let response = convert_type( + &self.prost_method.output_proto_type, + &self.prost_method.output_type, + ); (request, response) } } @@ -176,7 +220,10 @@ impl prost_build::ServiceGenerator for ServiceGenerator { .disable_comments(self.builder.disable_comments.clone()) .use_arc_self(self.builder.use_arc_self) .generate_default_stubs(self.builder.generate_default_stubs) - .generate_server(&service, &self.builder.proto_path); + .generate_server( + &TonicBuildService::new(service.clone(), self.builder.compile_settings.clone()), + &self.builder.proto_path, + ); self.servers.extend(server); } @@ -188,7 +235,10 @@ impl prost_build::ServiceGenerator for ServiceGenerator { .attributes(self.builder.client_attributes.clone()) .disable_comments(self.builder.disable_comments.clone()) .build_transport(self.builder.build_transport) - .generate_client(&service, &self.builder.proto_path); + .generate_client( + &TonicBuildService::new(service, self.builder.compile_settings.clone()), + &self.builder.proto_path, + ); self.clients.extend(client); } @@ -252,6 +302,7 @@ pub struct Builder { pub(crate) disable_comments: HashSet, pub(crate) use_arc_self: bool, pub(crate) generate_default_stubs: bool, + pub(crate) compile_settings: CompileSettings, out_dir: Option, } @@ -524,6 +575,16 @@ impl Builder { self } + /// Override the default codec. + /// + /// If set, writes `{codec_path}::default()` in generated code wherever a codec is created. + /// + /// This defaults to `"tonic::codec::ProstCodec"` + pub fn codec_path(mut self, codec_path: impl Into) -> Self { + self.compile_settings.codec_path = codec_path.into(); + self + } + /// Compile the .proto files and execute code generation. pub fn compile( self, diff --git a/tonic/src/codec/compression.rs b/tonic/src/codec/compression.rs index 70d758415..e00b8ca8f 100644 --- a/tonic/src/codec/compression.rs +++ b/tonic/src/codec/compression.rs @@ -1,4 +1,3 @@ -use super::encode::BUFFER_SIZE; use crate::{metadata::MetadataValue, Status}; use bytes::{Buf, BytesMut}; #[cfg(feature = "gzip")] @@ -70,6 +69,14 @@ impl EnabledCompressionEncodings { } } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) struct CompressionSettings { + pub(crate) encoding: CompressionEncoding, + /// buffer_growth_interval controls memory growth for internal buffers to balance resizing cost against memory waste. + /// The default buffer growth interval is 8 kilobytes. + pub(crate) buffer_growth_interval: usize, +} + /// The compression encodings Tonic supports. #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[non_exhaustive] @@ -195,20 +202,22 @@ fn split_by_comma(s: &str) -> impl Iterator { } /// Compress `len` bytes from `decompressed_buf` into `out_buf`. +/// buffer_size_increment is a hint to control the growth of out_buf versus the cost of resizing it. #[allow(unused_variables, unreachable_code)] pub(crate) fn compress( - encoding: CompressionEncoding, + settings: CompressionSettings, decompressed_buf: &mut BytesMut, out_buf: &mut BytesMut, len: usize, ) -> Result<(), std::io::Error> { - let capacity = ((len / BUFFER_SIZE) + 1) * BUFFER_SIZE; + let buffer_growth_interval = settings.buffer_growth_interval; + let capacity = ((len / buffer_growth_interval) + 1) * buffer_growth_interval; out_buf.reserve(capacity); #[cfg(any(feature = "gzip", feature = "zstd"))] let mut out_writer = bytes::BufMut::writer(out_buf); - match encoding { + match settings.encoding { #[cfg(feature = "gzip")] CompressionEncoding::Gzip => { let mut gzip_encoder = GzEncoder::new( @@ -237,19 +246,21 @@ pub(crate) fn compress( /// Decompress `len` bytes from `compressed_buf` into `out_buf`. #[allow(unused_variables, unreachable_code)] pub(crate) fn decompress( - encoding: CompressionEncoding, + settings: CompressionSettings, compressed_buf: &mut BytesMut, out_buf: &mut BytesMut, len: usize, ) -> Result<(), std::io::Error> { + let buffer_growth_interval = settings.buffer_growth_interval; let estimate_decompressed_len = len * 2; - let capacity = ((estimate_decompressed_len / BUFFER_SIZE) + 1) * BUFFER_SIZE; + let capacity = + ((estimate_decompressed_len / buffer_growth_interval) + 1) * buffer_growth_interval; out_buf.reserve(capacity); #[cfg(any(feature = "gzip", feature = "zstd"))] let mut out_writer = bytes::BufMut::writer(out_buf); - match encoding { + match settings.encoding { #[cfg(feature = "gzip")] CompressionEncoding::Gzip => { let mut gzip_decoder = GzDecoder::new(&compressed_buf[0..len]); diff --git a/tonic/src/codec/decode.rs b/tonic/src/codec/decode.rs index cb88a0649..081f6193d 100644 --- a/tonic/src/codec/decode.rs +++ b/tonic/src/codec/decode.rs @@ -1,5 +1,5 @@ -use super::compression::{decompress, CompressionEncoding}; -use super::{DecodeBuf, Decoder, DEFAULT_MAX_RECV_MESSAGE_SIZE, HEADER_SIZE}; +use super::compression::{decompress, CompressionEncoding, CompressionSettings}; +use super::{BufferSettings, DecodeBuf, Decoder, DEFAULT_MAX_RECV_MESSAGE_SIZE, HEADER_SIZE}; use crate::{body::BoxBody, metadata::MetadataMap, Code, Status}; use bytes::{Buf, BufMut, BytesMut}; use http::StatusCode; @@ -13,8 +13,6 @@ use std::{ use tokio_stream::Stream; use tracing::{debug, trace}; -const BUFFER_SIZE: usize = 8 * 1024; - /// Streaming requests and responses. /// /// This will wrap some inner [`Body`] and [`Decoder`] and provide an interface @@ -118,6 +116,7 @@ impl Streaming { B::Error: Into, D: Decoder + Send + 'static, { + let buffer_size = decoder.buffer_settings().buffer_size; Self { decoder: Box::new(decoder), inner: StreamingInner { @@ -127,7 +126,7 @@ impl Streaming { .boxed_unsync(), state: State::ReadHeader, direction, - buf: BytesMut::with_capacity(BUFFER_SIZE), + buf: BytesMut::with_capacity(buffer_size), trailers: None, decompress_buf: BytesMut::new(), encoding, @@ -138,7 +137,10 @@ impl Streaming { } impl StreamingInner { - fn decode_chunk(&mut self) -> Result>, Status> { + fn decode_chunk( + &mut self, + buffer_settings: BufferSettings, + ) -> Result>, Status> { if let State::ReadHeader = self.state { if self.buf.remaining() < HEADER_SIZE { return Ok(None); @@ -205,8 +207,15 @@ impl StreamingInner { let decode_buf = if let Some(encoding) = compression { self.decompress_buf.clear(); - if let Err(err) = decompress(encoding, &mut self.buf, &mut self.decompress_buf, len) - { + if let Err(err) = decompress( + CompressionSettings { + encoding, + buffer_growth_interval: buffer_settings.buffer_size, + }, + &mut self.buf, + &mut self.decompress_buf, + len, + ) { let message = if let Direction::Response(status) = self.direction { format!( "Error decompressing: {}, while receiving response with status: {}", @@ -364,7 +373,7 @@ impl Streaming { } fn decode_chunk(&mut self) -> Result, Status> { - match self.inner.decode_chunk()? { + match self.inner.decode_chunk(self.decoder.buffer_settings())? { Some(mut decode_buf) => match self.decoder.decode(&mut decode_buf)? { Some(msg) => { self.inner.state = State::ReadHeader; diff --git a/tonic/src/codec/encode.rs b/tonic/src/codec/encode.rs index 13eb2c96d..396f77399 100644 --- a/tonic/src/codec/encode.rs +++ b/tonic/src/codec/encode.rs @@ -1,5 +1,7 @@ -use super::compression::{compress, CompressionEncoding, SingleMessageCompressionOverride}; -use super::{EncodeBuf, Encoder, DEFAULT_MAX_SEND_MESSAGE_SIZE, HEADER_SIZE}; +use super::compression::{ + compress, CompressionEncoding, CompressionSettings, SingleMessageCompressionOverride, +}; +use super::{BufferSettings, EncodeBuf, Encoder, DEFAULT_MAX_SEND_MESSAGE_SIZE, HEADER_SIZE}; use crate::{Code, Status}; use bytes::{BufMut, Bytes, BytesMut}; use http::HeaderMap; @@ -11,9 +13,6 @@ use std::{ }; use tokio_stream::{Stream, StreamExt}; -pub(super) const BUFFER_SIZE: usize = 8 * 1024; -const YIELD_THRESHOLD: usize = 32 * 1024; - pub(crate) fn encode_server( encoder: T, source: U, @@ -90,7 +89,8 @@ where compression_override: SingleMessageCompressionOverride, max_message_size: Option, ) -> Self { - let buf = BytesMut::with_capacity(BUFFER_SIZE); + let buffer_settings = encoder.buffer_settings(); + let buf = BytesMut::with_capacity(buffer_settings.buffer_size); let compression_encoding = if compression_override == SingleMessageCompressionOverride::Disable { @@ -100,7 +100,7 @@ where }; let uncompression_buf = if compression_encoding.is_some() { - BytesMut::with_capacity(BUFFER_SIZE) + BytesMut::with_capacity(buffer_settings.buffer_size) } else { BytesMut::new() }; @@ -132,6 +132,7 @@ where buf, uncompression_buf, } = self.project(); + let buffer_settings = encoder.buffer_settings(); loop { match source.as_mut().poll_next(cx) { @@ -151,12 +152,13 @@ where uncompression_buf, *compression_encoding, *max_message_size, + buffer_settings, item, ) { return Poll::Ready(Some(Err(status))); } - if buf.len() >= YIELD_THRESHOLD { + if buf.len() >= buffer_settings.yield_threshold { return Poll::Ready(Some(Ok(buf.split_to(buf.len()).freeze()))); } } @@ -174,6 +176,7 @@ fn encode_item( uncompression_buf: &mut BytesMut, compression_encoding: Option, max_message_size: Option, + buffer_settings: BufferSettings, item: T::Item, ) -> Result<(), Status> where @@ -195,8 +198,16 @@ where let uncompressed_len = uncompression_buf.len(); - compress(encoding, uncompression_buf, buf, uncompressed_len) - .map_err(|err| Status::internal(format!("Error compressing: {}", err)))?; + compress( + CompressionSettings { + encoding, + buffer_growth_interval: buffer_settings.buffer_size, + }, + uncompression_buf, + buf, + uncompressed_len, + ) + .map_err(|err| Status::internal(format!("Error compressing: {}", err)))?; } else { encoder .encode(item, &mut EncodeBuf::new(buf)) diff --git a/tonic/src/codec/mod.rs b/tonic/src/codec/mod.rs index 306621329..998899a6c 100644 --- a/tonic/src/codec/mod.rs +++ b/tonic/src/codec/mod.rs @@ -22,6 +22,75 @@ pub use self::decode::Streaming; #[cfg_attr(docsrs, doc(cfg(feature = "prost")))] pub use self::prost::ProstCodec; +/// Unless overridden, this is the buffer size used for encoding requests. +/// This is spent per-rpc, so you may wish to adjust it. The default is +/// pretty good for most uses, but if you have a ton of concurrent rpcs +/// you may find it too expensive. +const DEFAULT_CODEC_BUFFER_SIZE: usize = 8 * 1024; +const DEFAULT_YIELD_THRESHOLD: usize = 32 * 1024; + +/// Settings for how tonic allocates and grows buffers. +/// +/// Tonic eagerly allocates the buffer_size per RPC, and grows +/// the buffer by buffer_size increments to handle larger messages. +/// Buffer size defaults to 8KiB. +/// +/// Example: +/// ```ignore +/// Buffer start: | 8kb | +/// Message received: | 24612 bytes | +/// Buffer grows: | 8kb | 8kb | 8kb | 8kb | +/// ``` +/// +/// The buffer grows to the next largest buffer_size increment of +/// 32768 to hold 24612 bytes, which is just slightly too large for +/// the previous buffer increment of 24576. +/// +/// If you use a smaller buffer size you will waste less memory, but +/// you will allocate more frequently. If one way or the other matters +/// more to you, you may wish to customize your tonic Codec (see +/// codec_buffers example). +/// +/// Yield threshold is an optimization for streaming rpcs. Sometimes +/// you may have many small messages ready to send. When they are ready, +/// it is a much more efficient use of system resources to batch them +/// together into one larger send(). The yield threshold controls how +/// much you want to bulk up such a batch of ready-to-send messages. +/// The larger your yield threshold the more you will batch - and +/// consequentially allocate contiguous memory, which might be relevant +/// if you're considering large numbers here. +/// If your server streaming rpc does not reach the yield threshold +/// before it reaches Poll::Pending (meaning, it's waiting for more +/// data from wherever you're streaming from) then Tonic will just send +/// along a smaller batch. Yield threshold is an upper-bound, it will +/// not affect the responsiveness of your streaming rpc (for reasonable +/// sizes of yield threshold). +/// Yield threshold defaults to 32 KiB. +#[derive(Clone, Copy, Debug)] +pub struct BufferSettings { + buffer_size: usize, + yield_threshold: usize, +} + +impl BufferSettings { + /// Create a new `BufferSettings` + pub fn new(buffer_size: usize, yield_threshold: usize) -> Self { + Self { + buffer_size, + yield_threshold, + } + } +} + +impl Default for BufferSettings { + fn default() -> Self { + Self { + buffer_size: DEFAULT_CODEC_BUFFER_SIZE, + yield_threshold: DEFAULT_YIELD_THRESHOLD, + } + } +} + // 5 bytes const HEADER_SIZE: usize = // compression flag @@ -63,6 +132,11 @@ pub trait Encoder { /// Encodes a message into the provided buffer. fn encode(&mut self, item: Self::Item, dst: &mut EncodeBuf<'_>) -> Result<(), Self::Error>; + + /// Controls how tonic creates and expands encode buffers. + fn buffer_settings(&self) -> BufferSettings { + BufferSettings::default() + } } /// Decodes gRPC message types @@ -79,4 +153,9 @@ pub trait Decoder { /// is no need to get the length from the bytes, gRPC framing is handled /// for you. fn decode(&mut self, src: &mut DecodeBuf<'_>) -> Result, Self::Error>; + + /// Controls how tonic creates and expands decode buffers. + fn buffer_settings(&self) -> BufferSettings { + BufferSettings::default() + } } diff --git a/tonic/src/codec/prost.rs b/tonic/src/codec/prost.rs index aa872a9ba..217934e9e 100644 --- a/tonic/src/codec/prost.rs +++ b/tonic/src/codec/prost.rs @@ -1,4 +1,4 @@ -use super::{Codec, DecodeBuf, Decoder, Encoder}; +use super::{BufferSettings, Codec, DecodeBuf, Decoder, Encoder}; use crate::codec::EncodeBuf; use crate::{Code, Status}; use prost::Message; @@ -10,9 +10,41 @@ pub struct ProstCodec { _pd: PhantomData<(T, U)>, } +impl ProstCodec { + /// Configure a ProstCodec with encoder/decoder buffer settings. This is used to control + /// how memory is allocated and grows per RPC. + pub fn new() -> Self { + Self { _pd: PhantomData } + } +} + impl Default for ProstCodec { fn default() -> Self { - Self { _pd: PhantomData } + Self::new() + } +} + +impl ProstCodec +where + T: Message + Send + 'static, + U: Message + Default + Send + 'static, +{ + /// A tool for building custom codecs based on prost encoding and decoding. + /// See the codec_buffers example for one possible way to use this. + pub fn raw_encoder(buffer_settings: BufferSettings) -> ::Encoder { + ProstEncoder { + _pd: PhantomData, + buffer_settings, + } + } + + /// A tool for building custom codecs based on prost encoding and decoding. + /// See the codec_buffers example for one possible way to use this. + pub fn raw_decoder(buffer_settings: BufferSettings) -> ::Decoder { + ProstDecoder { + _pd: PhantomData, + buffer_settings, + } } } @@ -28,17 +60,36 @@ where type Decoder = ProstDecoder; fn encoder(&mut self) -> Self::Encoder { - ProstEncoder(PhantomData) + ProstEncoder { + _pd: PhantomData, + buffer_settings: BufferSettings::default(), + } } fn decoder(&mut self) -> Self::Decoder { - ProstDecoder(PhantomData) + ProstDecoder { + _pd: PhantomData, + buffer_settings: BufferSettings::default(), + } } } /// A [`Encoder`] that knows how to encode `T`. #[derive(Debug, Clone, Default)] -pub struct ProstEncoder(PhantomData); +pub struct ProstEncoder { + _pd: PhantomData, + buffer_settings: BufferSettings, +} + +impl ProstEncoder { + /// Get a new encoder with explicit buffer settings + pub fn new(buffer_settings: BufferSettings) -> Self { + Self { + _pd: PhantomData, + buffer_settings, + } + } +} impl Encoder for ProstEncoder { type Item = T; @@ -50,11 +101,28 @@ impl Encoder for ProstEncoder { Ok(()) } + + fn buffer_settings(&self) -> BufferSettings { + self.buffer_settings + } } /// A [`Decoder`] that knows how to decode `U`. #[derive(Debug, Clone, Default)] -pub struct ProstDecoder(PhantomData); +pub struct ProstDecoder { + _pd: PhantomData, + buffer_settings: BufferSettings, +} + +impl ProstDecoder { + /// Get a new decoder with explicit buffer settings + pub fn new(buffer_settings: BufferSettings) -> Self { + Self { + _pd: PhantomData, + buffer_settings, + } + } +} impl Decoder for ProstDecoder { type Item = U; @@ -67,6 +135,10 @@ impl Decoder for ProstDecoder { Ok(item) } + + fn buffer_settings(&self) -> BufferSettings { + self.buffer_settings + } } fn from_decode_error(error: prost::DecodeError) -> crate::Status { @@ -244,6 +316,10 @@ mod tests { buf.put(&item[..]); Ok(()) } + + fn buffer_settings(&self) -> crate::codec::BufferSettings { + Default::default() + } } #[derive(Debug, Clone, Default)] @@ -258,6 +334,10 @@ mod tests { buf.advance(LEN); Ok(Some(out)) } + + fn buffer_settings(&self) -> crate::codec::BufferSettings { + Default::default() + } } mod body { From 8f078fe935c389e3361ad1666567b58fcd78410d Mon Sep 17 00:00:00 2001 From: tottoto Date: Sat, 24 Feb 2024 01:14:50 +0900 Subject: [PATCH 17/20] chore(tls): Remove unused tls field when tls feature is not used (#1498) --- tonic/src/transport/service/connector.rs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/tonic/src/transport/service/connector.rs b/tonic/src/transport/service/connector.rs index 6645696ea..801ac65cc 100644 --- a/tonic/src/transport/service/connector.rs +++ b/tonic/src/transport/service/connector.rs @@ -12,20 +12,15 @@ pub(crate) struct Connector { inner: C, #[cfg(feature = "tls")] tls: Option, - #[cfg(not(feature = "tls"))] - #[allow(dead_code)] - tls: Option<()>, } impl Connector { - #[cfg(not(feature = "tls"))] - pub(crate) fn new(inner: C) -> Self { - Self { inner, tls: None } - } - - #[cfg(feature = "tls")] - pub(crate) fn new(inner: C, tls: Option) -> Self { - Self { inner, tls } + pub(crate) fn new(inner: C, #[cfg(feature = "tls")] tls: Option) -> Self { + Self { + inner, + #[cfg(feature = "tls")] + tls, + } } #[cfg(feature = "tls-roots-common")] From 233711a72a63f746d386916990fc817e36eabd80 Mon Sep 17 00:00:00 2001 From: tottoto Date: Sat, 24 Feb 2024 01:15:03 +0900 Subject: [PATCH 18/20] chore: Update to actions/checkout@v4 (#1508) --- .github/workflows/CI.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 52cd882b2..b98d2add7 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -15,7 +15,7 @@ jobs: rustfmt: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: hecrj/setup-rust-action@v2 with: components: rustfmt @@ -25,13 +25,13 @@ jobs: name: cargo-deny check runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: EmbarkStudios/cargo-deny-action@v1 codegen: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: hecrj/setup-rust-action@v2 - name: Install protoc uses: taiki-e/install-action@v2 @@ -51,7 +51,7 @@ jobs: - os: windows-latest option: --exclude-features uds steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: toolchain: nightly-2024-02-06 @@ -70,7 +70,7 @@ jobs: matrix: os: [ubuntu-latest, macOS-latest, windows-latest] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: hecrj/setup-rust-action@v2 - uses: taiki-e/install-action@cargo-hack - name: Install protoc @@ -87,7 +87,7 @@ jobs: name: Check MSRV runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: hecrj/setup-rust-action@v2 with: rust-version: "1.70" # msrv @@ -107,7 +107,7 @@ jobs: matrix: os: [ubuntu-latest, macOS-latest, windows-latest] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: hecrj/setup-rust-action@v2 - name: Install protoc uses: taiki-e/install-action@v2 @@ -125,7 +125,7 @@ jobs: matrix: os: [ubuntu-latest, macOS-latest, windows-latest] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: hecrj/setup-rust-action@v2 - name: Install protoc uses: taiki-e/install-action@v2 From 0522f48cff002f7f2c8c539183f116cacb2d0e12 Mon Sep 17 00:00:00 2001 From: ajwerner Date: Thu, 29 Feb 2024 13:00:42 -0500 Subject: [PATCH 19/20] transport: extend ClientTlsConfig to omit ALPN (#1640) Sometimes servers are secured with tls and speak gRPC, but don't perform ALPN protocol negotation. Most other gRPC implementations out there, as far as I can tell will just assume that the remote server is speaking h2 if there is ALPN. Tonic is strict in this regard. This patch takes the conservative approach of allowing users to opt into assuming that the remote server is running h2. A more aggressive patch in the future might be to invert the default. --- tonic/src/transport/channel/endpoint.rs | 24 +++++++++++++++++++++++- tonic/src/transport/channel/tls.rs | 18 +++++++++++++++++- tonic/src/transport/service/connector.rs | 14 ++++++++++++-- tonic/src/transport/service/tls.rs | 13 ++++++++++--- 4 files changed, 62 insertions(+), 7 deletions(-) diff --git a/tonic/src/transport/channel/endpoint.rs b/tonic/src/transport/channel/endpoint.rs index 598e89b70..597b4bc4d 100644 --- a/tonic/src/transport/channel/endpoint.rs +++ b/tonic/src/transport/channel/endpoint.rs @@ -24,6 +24,11 @@ pub struct Endpoint { pub(crate) rate_limit: Option<(u64, Duration)>, #[cfg(feature = "tls")] pub(crate) tls: Option, + // Only applies if the tls config is not explicitly set. This allows users + // to connect to a server that doesn't support ALPN while using the + // tls-roots-common feature for setting up TLS. + #[cfg(feature = "tls-roots-common")] + pub(crate) tls_assume_http2: bool, pub(crate) buffer_size: Option, pub(crate) init_stream_window_size: Option, pub(crate) init_connection_window_size: Option, @@ -250,6 +255,18 @@ impl Endpoint { }) } + /// Configures TLS to assume that the server offers HTTP/2 even if it + /// doesn't perform ALPN negotiation. This only applies if a tls_config has + /// not been set. + #[cfg(feature = "tls-roots-common")] + #[cfg_attr(docsrs, doc(cfg(feature = "tls-roots-common")))] + pub fn tls_assume_http2(self, assume_http2: bool) -> Self { + Endpoint { + tls_assume_http2: assume_http2, + ..self + } + } + /// Set the value of `TCP_NODELAY` option for accepted connections. Enabled by default. pub fn tcp_nodelay(self, enabled: bool) -> Self { Endpoint { @@ -302,9 +319,12 @@ impl Endpoint { } pub(crate) fn connector(&self, c: C) -> service::Connector { - #[cfg(feature = "tls")] + #[cfg(all(feature = "tls", not(feature = "tls-roots-common")))] let connector = service::Connector::new(c, self.tls.clone()); + #[cfg(all(feature = "tls", feature = "tls-roots-common"))] + let connector = service::Connector::new(c, self.tls.clone(), self.tls_assume_http2); + #[cfg(not(feature = "tls"))] let connector = service::Connector::new(c); @@ -424,6 +444,8 @@ impl From for Endpoint { timeout: None, #[cfg(feature = "tls")] tls: None, + #[cfg(feature = "tls-roots-common")] + tls_assume_http2: false, buffer_size: None, init_stream_window_size: None, init_connection_window_size: None, diff --git a/tonic/src/transport/channel/tls.rs b/tonic/src/transport/channel/tls.rs index cead6867a..346071fad 100644 --- a/tonic/src/transport/channel/tls.rs +++ b/tonic/src/transport/channel/tls.rs @@ -12,6 +12,7 @@ pub struct ClientTlsConfig { domain: Option, cert: Option, identity: Option, + assume_http2: bool, } impl fmt::Debug for ClientTlsConfig { @@ -31,6 +32,7 @@ impl ClientTlsConfig { domain: None, cert: None, identity: None, + assume_http2: false, } } @@ -58,11 +60,25 @@ impl ClientTlsConfig { } } + /// If true, the connector should assume that the server supports HTTP/2, + /// even if it doesn't provide protocol negotiation via ALPN. + pub fn assume_http2(self, assume_http2: bool) -> Self { + ClientTlsConfig { + assume_http2, + ..self + } + } + pub(crate) fn tls_connector(&self, uri: Uri) -> Result { let domain = match &self.domain { Some(domain) => domain, None => uri.host().ok_or_else(Error::new_invalid_uri)?, }; - TlsConnector::new(self.cert.clone(), self.identity.clone(), domain) + TlsConnector::new( + self.cert.clone(), + self.identity.clone(), + domain, + self.assume_http2, + ) } } diff --git a/tonic/src/transport/service/connector.rs b/tonic/src/transport/service/connector.rs index 801ac65cc..8b7e7f63a 100644 --- a/tonic/src/transport/service/connector.rs +++ b/tonic/src/transport/service/connector.rs @@ -12,14 +12,24 @@ pub(crate) struct Connector { inner: C, #[cfg(feature = "tls")] tls: Option, + // When connecting to a URI with the https scheme, assume that the server + // is capable of speaking HTTP/2 even if it doesn't offer ALPN. + #[cfg(feature = "tls-roots-common")] + assume_http2: bool, } impl Connector { - pub(crate) fn new(inner: C, #[cfg(feature = "tls")] tls: Option) -> Self { + pub(crate) fn new( + inner: C, + #[cfg(feature = "tls")] tls: Option, + #[cfg(feature = "tls-roots-common")] assume_http2: bool, + ) -> Self { Self { inner, #[cfg(feature = "tls")] tls, + #[cfg(feature = "tls-roots-common")] + assume_http2, } } @@ -34,7 +44,7 @@ impl Connector { _ => return None, }; - TlsConnector::new(None, None, host).ok() + TlsConnector::new(None, None, host, self.assume_http2).ok() } } diff --git a/tonic/src/transport/service/tls.rs b/tonic/src/transport/service/tls.rs index 3a6ef62fb..96e1fe652 100644 --- a/tonic/src/transport/service/tls.rs +++ b/tonic/src/transport/service/tls.rs @@ -30,6 +30,7 @@ enum TlsError { pub(crate) struct TlsConnector { config: Arc, domain: Arc>, + assume_http2: bool, } impl TlsConnector { @@ -37,6 +38,7 @@ impl TlsConnector { ca_cert: Option, identity: Option, domain: &str, + assume_http2: bool, ) -> Result { let builder = ClientConfig::builder(); let mut roots = RootCertStore::empty(); @@ -64,6 +66,7 @@ impl TlsConnector { Ok(Self { config: Arc::new(config), domain: Arc::new(ServerName::try_from(domain)?.to_owned()), + assume_http2, }) } @@ -75,11 +78,15 @@ impl TlsConnector { .connect(self.domain.as_ref().to_owned(), io) .await?; + // Generally we require ALPN to be negotiated, but if the user has + // explicitly set `assume_http2` to true, we'll allow it to be missing. let (_, session) = io.get_ref(); - if session.alpn_protocol() != Some(ALPN_H2) { - return Err(TlsError::H2NotNegotiated)?; + let alpn_protocol = session.alpn_protocol(); + if alpn_protocol != Some(ALPN_H2) { + if alpn_protocol.is_some() || !self.assume_http2 { + return Err(TlsError::H2NotNegotiated.into()); + } } - Ok(BoxedIo::new(io)) } } From 8a53392190937a7b7b2ff69894c29d5aa39067a1 Mon Sep 17 00:00:00 2001 From: tottoto Date: Fri, 1 Mar 2024 03:23:52 +0900 Subject: [PATCH 20/20] chore: Apply small refactoring (#1642) * chore(interop): Replace map clone with cloned * chore(examples): Remove importing prelude trait --- examples/src/tls_rustls/client.rs | 2 -- interop/src/server.rs | 7 ++----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/examples/src/tls_rustls/client.rs b/examples/src/tls_rustls/client.rs index 503526234..23d6e8130 100644 --- a/examples/src/tls_rustls/client.rs +++ b/examples/src/tls_rustls/client.rs @@ -5,8 +5,6 @@ pub mod pb { tonic::include_proto!("/grpc.examples.unaryecho"); } -use std::iter::FromIterator; - use hyper::{client::HttpConnector, Uri}; use pb::{echo_client::EchoClient, EchoRequest}; use tokio_rustls::rustls::{ClientConfig, RootCertStore}; diff --git a/interop/src/server.rs b/interop/src/server.rs index 6ffb16309..b32468866 100644 --- a/interop/src/server.rs +++ b/interop/src/server.rs @@ -194,15 +194,12 @@ where } fn call(&mut self, req: http::Request) -> Self::Future { - let echo_header = req - .headers() - .get("x-grpc-test-echo-initial") - .map(Clone::clone); + let echo_header = req.headers().get("x-grpc-test-echo-initial").cloned(); let echo_trailer = req .headers() .get("x-grpc-test-echo-trailing-bin") - .map(Clone::clone) + .cloned() .map(|v| (HeaderName::from_static("x-grpc-test-echo-trailing-bin"), v)); let call = self.inner.call(req);