diff --git a/tests/compression/Cargo.toml b/tests/compression/Cargo.toml index 31ac8c60b..7371ca3ae 100644 --- a/tests/compression/Cargo.toml +++ b/tests/compression/Cargo.toml @@ -16,7 +16,7 @@ pin-project = "1.0" prost = "0.13" tokio = {version = "1.0", features = ["macros", "rt-multi-thread", "net"]} tokio-stream = "0.1" -tonic = {path = "../../tonic", features = ["gzip", "zstd"]} +tonic = {path = "../../tonic", features = ["gzip", "deflate", "zstd"]} tower = "0.5" tower-http = {version = "0.6", features = ["map-response-body", "map-request-body"]} diff --git a/tests/compression/src/bidirectional_stream.rs b/tests/compression/src/bidirectional_stream.rs index 898fe5b05..3d7fe46f5 100644 --- a/tests/compression/src/bidirectional_stream.rs +++ b/tests/compression/src/bidirectional_stream.rs @@ -6,6 +6,7 @@ util::parametrized_tests! { client_enabled_server_enabled, zstd: CompressionEncoding::Zstd, gzip: CompressionEncoding::Gzip, + deflate: CompressionEncoding::Deflate, } #[allow(dead_code)] @@ -34,6 +35,7 @@ async fn client_enabled_server_enabled(encoding: CompressionEncoding) { let expected = match self.encoding { CompressionEncoding::Gzip => "gzip", CompressionEncoding::Zstd => "zstd", + CompressionEncoding::Deflate => "deflate", _ => panic!("unexpected encoding {:?}", self.encoding), }; assert_eq!(req.headers().get("grpc-encoding").unwrap(), expected); @@ -86,6 +88,7 @@ async fn client_enabled_server_enabled(encoding: CompressionEncoding) { let expected = match encoding { CompressionEncoding::Gzip => "gzip", CompressionEncoding::Zstd => "zstd", + CompressionEncoding::Deflate => "deflate", _ => panic!("unexpected encoding {:?}", encoding), }; assert_eq!(res.metadata().get("grpc-encoding").unwrap(), expected); diff --git a/tests/compression/src/client_stream.rs b/tests/compression/src/client_stream.rs index 9402c75f2..fe31aa01c 100644 --- a/tests/compression/src/client_stream.rs +++ b/tests/compression/src/client_stream.rs @@ -6,6 +6,7 @@ util::parametrized_tests! { client_enabled_server_enabled, zstd: CompressionEncoding::Zstd, gzip: CompressionEncoding::Gzip, + deflate: CompressionEncoding::Deflate, } #[allow(dead_code)] @@ -31,6 +32,7 @@ async fn client_enabled_server_enabled(encoding: CompressionEncoding) { let expected = match self.encoding { CompressionEncoding::Gzip => "gzip", CompressionEncoding::Zstd => "zstd", + CompressionEncoding::Deflate => "deflate", _ => panic!("unexpected encoding {:?}", self.encoding), }; assert_eq!(req.headers().get("grpc-encoding").unwrap(), expected); @@ -77,6 +79,7 @@ util::parametrized_tests! { client_disabled_server_enabled, zstd: CompressionEncoding::Zstd, gzip: CompressionEncoding::Gzip, + deflate: CompressionEncoding::Deflate, } #[allow(dead_code)] @@ -127,6 +130,7 @@ util::parametrized_tests! { client_enabled_server_disabled, zstd: CompressionEncoding::Zstd, gzip: CompressionEncoding::Gzip, + deflate: CompressionEncoding::Deflate, } #[allow(dead_code)] @@ -156,6 +160,7 @@ async fn client_enabled_server_disabled(encoding: CompressionEncoding) { let expected = match encoding { CompressionEncoding::Gzip => "gzip", CompressionEncoding::Zstd => "zstd", + CompressionEncoding::Deflate => "deflate", _ => panic!("unexpected encoding {:?}", encoding), }; assert_eq!( @@ -171,6 +176,7 @@ util::parametrized_tests! { compressing_response_from_client_stream, zstd: CompressionEncoding::Zstd, gzip: CompressionEncoding::Gzip, + deflate: CompressionEncoding::Deflate, } #[allow(dead_code)] @@ -211,6 +217,7 @@ async fn compressing_response_from_client_stream(encoding: CompressionEncoding) let expected = match encoding { CompressionEncoding::Gzip => "gzip", CompressionEncoding::Zstd => "zstd", + CompressionEncoding::Deflate => "deflate", _ => panic!("unexpected encoding {:?}", encoding), }; assert_eq!(res.metadata().get("grpc-encoding").unwrap(), expected); diff --git a/tests/compression/src/compressing_request.rs b/tests/compression/src/compressing_request.rs index 87a02de64..ed64f4e9a 100644 --- a/tests/compression/src/compressing_request.rs +++ b/tests/compression/src/compressing_request.rs @@ -6,6 +6,7 @@ util::parametrized_tests! { client_enabled_server_enabled, zstd: CompressionEncoding::Zstd, gzip: CompressionEncoding::Gzip, + deflate: CompressionEncoding::Deflate, } #[allow(dead_code)] @@ -31,6 +32,7 @@ async fn client_enabled_server_enabled(encoding: CompressionEncoding) { let expected = match self.encoding { CompressionEncoding::Gzip => "gzip", CompressionEncoding::Zstd => "zstd", + CompressionEncoding::Deflate => "deflate", _ => panic!("unexpected encoding {:?}", self.encoding), }; assert_eq!(req.headers().get("grpc-encoding").unwrap(), expected); @@ -81,6 +83,7 @@ util::parametrized_tests! { client_enabled_server_enabled_multi_encoding, zstd: CompressionEncoding::Zstd, gzip: CompressionEncoding::Gzip, + deflate: CompressionEncoding::Deflate, } #[allow(dead_code)] @@ -89,12 +92,13 @@ async fn client_enabled_server_enabled_multi_encoding(encoding: CompressionEncod let svc = test_server::TestServer::new(Svc::default()) .accept_compressed(CompressionEncoding::Gzip) - .accept_compressed(CompressionEncoding::Zstd); + .accept_compressed(CompressionEncoding::Zstd) + .accept_compressed(CompressionEncoding::Deflate); let request_bytes_counter = Arc::new(AtomicUsize::new(0)); fn assert_right_encoding(req: http::Request) -> http::Request { - let supported_encodings = ["gzip", "zstd"]; + let supported_encodings = ["gzip", "zstd", "deflate"]; let req_encoding = req.headers().get("grpc-encoding").unwrap(); assert!(supported_encodings.iter().any(|e| e == req_encoding)); @@ -141,6 +145,7 @@ parametrized_tests! { client_enabled_server_disabled, zstd: CompressionEncoding::Zstd, gzip: CompressionEncoding::Gzip, + deflate: CompressionEncoding::Deflate, } #[allow(dead_code)] @@ -171,6 +176,7 @@ async fn client_enabled_server_disabled(encoding: CompressionEncoding) { let expected = match encoding { CompressionEncoding::Gzip => "gzip", CompressionEncoding::Zstd => "zstd", + CompressionEncoding::Deflate => "deflate", _ => panic!("unexpected encoding {:?}", encoding), }; assert_eq!( @@ -190,6 +196,7 @@ parametrized_tests! { client_mark_compressed_without_header_server_enabled, zstd: CompressionEncoding::Zstd, gzip: CompressionEncoding::Gzip, + deflate: CompressionEncoding::Deflate, } #[allow(dead_code)] diff --git a/tests/compression/src/compressing_response.rs b/tests/compression/src/compressing_response.rs index 86b0fa6cf..28ac95005 100644 --- a/tests/compression/src/compressing_response.rs +++ b/tests/compression/src/compressing_response.rs @@ -5,6 +5,7 @@ util::parametrized_tests! { client_enabled_server_enabled, zstd: CompressionEncoding::Zstd, gzip: CompressionEncoding::Gzip, + deflate: CompressionEncoding::Deflate, } #[allow(dead_code)] @@ -36,6 +37,7 @@ async fn client_enabled_server_enabled(encoding: CompressionEncoding) { let expected = match self.encoding { CompressionEncoding::Gzip => "gzip", CompressionEncoding::Zstd => "zstd", + CompressionEncoding::Deflate => "deflate", _ => panic!("unexpected encoding {:?}", self.encoding), }; assert_eq!( @@ -85,6 +87,7 @@ async fn client_enabled_server_enabled(encoding: CompressionEncoding) { let expected = match encoding { CompressionEncoding::Gzip => "gzip", CompressionEncoding::Zstd => "zstd", + CompressionEncoding::Deflate => "deflate", _ => panic!("unexpected encoding {:?}", encoding), }; @@ -100,6 +103,7 @@ util::parametrized_tests! { client_enabled_server_disabled, zstd: CompressionEncoding::Zstd, gzip: CompressionEncoding::Gzip, + deflate: CompressionEncoding::Deflate, } #[allow(dead_code)] @@ -175,7 +179,8 @@ async fn client_enabled_server_disabled_multi_encoding() { let mut client = test_client::TestClient::new(mock_io_channel(client).await) .accept_compressed(CompressionEncoding::Gzip) - .accept_compressed(CompressionEncoding::Zstd); + .accept_compressed(CompressionEncoding::Zstd) + .accept_compressed(CompressionEncoding::Deflate); let res = client.compress_output_unary(()).await.unwrap(); @@ -189,6 +194,7 @@ util::parametrized_tests! { client_disabled, zstd: CompressionEncoding::Zstd, gzip: CompressionEncoding::Gzip, + deflate: CompressionEncoding::Deflate, } #[allow(dead_code)] @@ -259,6 +265,7 @@ util::parametrized_tests! { server_replying_with_unsupported_encoding, zstd: CompressionEncoding::Zstd, gzip: CompressionEncoding::Gzip, + deflate: CompressionEncoding::Deflate, } #[allow(dead_code)] @@ -302,6 +309,7 @@ util::parametrized_tests! { disabling_compression_on_single_response, zstd: CompressionEncoding::Zstd, gzip: CompressionEncoding::Gzip, + deflate: CompressionEncoding::Deflate, } #[allow(dead_code)] @@ -344,6 +352,7 @@ async fn disabling_compression_on_single_response(encoding: CompressionEncoding) let expected = match encoding { CompressionEncoding::Gzip => "gzip", CompressionEncoding::Zstd => "zstd", + CompressionEncoding::Deflate => "deflate", _ => panic!("unexpected encoding {:?}", encoding), }; assert_eq!(res.metadata().get("grpc-encoding").unwrap(), expected); @@ -356,6 +365,7 @@ util::parametrized_tests! { disabling_compression_on_response_but_keeping_compression_on_stream, zstd: CompressionEncoding::Zstd, gzip: CompressionEncoding::Gzip, + deflate: CompressionEncoding::Deflate, } #[allow(dead_code)] @@ -400,6 +410,7 @@ async fn disabling_compression_on_response_but_keeping_compression_on_stream( let expected = match encoding { CompressionEncoding::Gzip => "gzip", CompressionEncoding::Zstd => "zstd", + CompressionEncoding::Deflate => "deflate", _ => panic!("unexpected encoding {:?}", encoding), }; assert_eq!(res.metadata().get("grpc-encoding").unwrap(), expected); @@ -425,6 +436,7 @@ util::parametrized_tests! { disabling_compression_on_response_from_client_stream, zstd: CompressionEncoding::Zstd, gzip: CompressionEncoding::Gzip, + deflate: CompressionEncoding::Deflate, } #[allow(dead_code)] @@ -469,6 +481,7 @@ async fn disabling_compression_on_response_from_client_stream(encoding: Compress let expected = match encoding { CompressionEncoding::Gzip => "gzip", CompressionEncoding::Zstd => "zstd", + CompressionEncoding::Deflate => "deflate", _ => panic!("unexpected encoding {:?}", encoding), }; assert_eq!(res.metadata().get("grpc-encoding").unwrap(), expected); diff --git a/tests/compression/src/server_stream.rs b/tests/compression/src/server_stream.rs index 9d0e4f390..775149ac2 100644 --- a/tests/compression/src/server_stream.rs +++ b/tests/compression/src/server_stream.rs @@ -6,6 +6,7 @@ util::parametrized_tests! { client_enabled_server_enabled, zstd: CompressionEncoding::Zstd, gzip: CompressionEncoding::Gzip, + deflate: CompressionEncoding::Deflate, } #[allow(dead_code)] @@ -45,6 +46,7 @@ async fn client_enabled_server_enabled(encoding: CompressionEncoding) { let expected = match encoding { CompressionEncoding::Gzip => "gzip", CompressionEncoding::Zstd => "zstd", + CompressionEncoding::Deflate => "deflate", _ => panic!("unexpected encoding {:?}", encoding), }; assert_eq!(res.metadata().get("grpc-encoding").unwrap(), expected); @@ -121,6 +123,7 @@ util::parametrized_tests! { client_enabled_server_disabled, zstd: CompressionEncoding::Zstd, gzip: CompressionEncoding::Gzip, + deflate: CompressionEncoding::Deflate, } #[allow(dead_code)] diff --git a/tests/compression/src/util.rs b/tests/compression/src/util.rs index 8c761ddf5..095e32290 100644 --- a/tests/compression/src/util.rs +++ b/tests/compression/src/util.rs @@ -161,6 +161,7 @@ impl AssertRightEncoding { let expected = match self.encoding { CompressionEncoding::Gzip => "gzip", CompressionEncoding::Zstd => "zstd", + CompressionEncoding::Deflate => "deflate", _ => panic!("unexpected encoding {:?}", self.encoding), }; assert_eq!(req.headers().get("grpc-encoding").unwrap(), expected); diff --git a/tonic/Cargo.toml b/tonic/Cargo.toml index 4b716dcfe..419c7d33a 100644 --- a/tonic/Cargo.toml +++ b/tonic/Cargo.toml @@ -25,6 +25,7 @@ version = "0.13.0" [features] codegen = ["dep:async-trait"] gzip = ["dep:flate2"] +deflate = ["dep:flate2"] zstd = ["dep:zstd"] default = ["transport", "codegen", "prost"] prost = ["dep:prost"] diff --git a/tonic/src/client/grpc.rs b/tonic/src/client/grpc.rs index 484db7fca..cd8a6b7d6 100644 --- a/tonic/src/client/grpc.rs +++ b/tonic/src/client/grpc.rs @@ -404,7 +404,7 @@ impl GrpcConfig { .headers_mut() .insert(CONTENT_TYPE, GRPC_CONTENT_TYPE); - #[cfg(any(feature = "gzip", feature = "zstd"))] + #[cfg(any(feature = "gzip", feature = "deflate", feature = "zstd"))] if let Some(encoding) = self.send_compression_encodings { request.headers_mut().insert( crate::codec::compression::ENCODING_HEADER, diff --git a/tonic/src/codec/compression.rs b/tonic/src/codec/compression.rs index 810813b98..b07e380a8 100644 --- a/tonic/src/codec/compression.rs +++ b/tonic/src/codec/compression.rs @@ -2,6 +2,8 @@ use crate::{metadata::MetadataValue, Status}; use bytes::{Buf, BufMut, BytesMut}; #[cfg(feature = "gzip")] use flate2::read::{GzDecoder, GzEncoder}; +#[cfg(feature = "deflate")] +use flate2::read::{ZlibDecoder, ZlibEncoder}; use std::fmt; #[cfg(feature = "zstd")] use zstd::stream::read::{Decoder, Encoder}; @@ -14,7 +16,7 @@ pub(crate) const ACCEPT_ENCODING_HEADER: &str = "grpc-accept-encoding"; /// Represents an ordered list of compression encodings that are enabled. #[derive(Debug, Default, Clone, Copy)] pub struct EnabledCompressionEncodings { - inner: [Option; 2], + inner: [Option; 3], } impl EnabledCompressionEncodings { @@ -85,6 +87,9 @@ pub enum CompressionEncoding { #[cfg(feature = "gzip")] Gzip, #[allow(missing_docs)] + #[cfg(feature = "deflate")] + Deflate, + #[allow(missing_docs)] #[cfg(feature = "zstd")] Zstd, } @@ -93,6 +98,8 @@ impl CompressionEncoding { pub(crate) const ENCODINGS: &'static [CompressionEncoding] = &[ #[cfg(feature = "gzip")] CompressionEncoding::Gzip, + #[cfg(feature = "deflate")] + CompressionEncoding::Deflate, #[cfg(feature = "zstd")] CompressionEncoding::Zstd, ]; @@ -112,6 +119,8 @@ impl CompressionEncoding { split_by_comma(header_value_str).find_map(|value| match value { #[cfg(feature = "gzip")] "gzip" => Some(CompressionEncoding::Gzip), + #[cfg(feature = "deflate")] + "deflate" => Some(CompressionEncoding::Deflate), #[cfg(feature = "zstd")] "zstd" => Some(CompressionEncoding::Zstd), _ => None, @@ -132,6 +141,10 @@ impl CompressionEncoding { b"gzip" if enabled_encodings.is_enabled(CompressionEncoding::Gzip) => { Ok(Some(CompressionEncoding::Gzip)) } + #[cfg(feature = "deflate")] + b"deflate" if enabled_encodings.is_enabled(CompressionEncoding::Deflate) => { + Ok(Some(CompressionEncoding::Deflate)) + } #[cfg(feature = "zstd")] b"zstd" if enabled_encodings.is_enabled(CompressionEncoding::Zstd) => { Ok(Some(CompressionEncoding::Zstd)) @@ -170,12 +183,14 @@ impl CompressionEncoding { match self { #[cfg(feature = "gzip")] CompressionEncoding::Gzip => "gzip", + #[cfg(feature = "deflate")] + CompressionEncoding::Deflate => "deflate", #[cfg(feature = "zstd")] CompressionEncoding::Zstd => "zstd", } } - #[cfg(any(feature = "gzip", feature = "zstd"))] + #[cfg(any(feature = "gzip", feature = "deflate", feature = "zstd"))] pub(crate) fn into_header_value(self) -> http::HeaderValue { http::HeaderValue::from_static(self.as_str()) } @@ -204,7 +219,7 @@ pub(crate) fn compress( let capacity = ((len / buffer_growth_interval) + 1) * buffer_growth_interval; out_buf.reserve(capacity); - #[cfg(any(feature = "gzip", feature = "zstd"))] + #[cfg(any(feature = "gzip", feature = "deflate", feature = "zstd"))] let mut out_writer = out_buf.writer(); match settings.encoding { @@ -217,6 +232,15 @@ pub(crate) fn compress( ); std::io::copy(&mut gzip_encoder, &mut out_writer)?; } + #[cfg(feature = "deflate")] + CompressionEncoding::Deflate => { + let mut deflate_encoder = ZlibEncoder::new( + &decompressed_buf[0..len], + // FIXME: support customizing the compression level + flate2::Compression::new(6), + ); + std::io::copy(&mut deflate_encoder, &mut out_writer)?; + } #[cfg(feature = "zstd")] CompressionEncoding::Zstd => { let mut zstd_encoder = Encoder::new( @@ -247,7 +271,7 @@ pub(crate) fn decompress( ((estimate_decompressed_len / buffer_growth_interval) + 1) * buffer_growth_interval; out_buf.reserve(capacity); - #[cfg(any(feature = "gzip", feature = "zstd"))] + #[cfg(any(feature = "gzip", feature = "deflate", feature = "zstd"))] let mut out_writer = out_buf.writer(); match settings.encoding { @@ -256,6 +280,11 @@ pub(crate) fn decompress( let mut gzip_decoder = GzDecoder::new(&compressed_buf[0..len]); std::io::copy(&mut gzip_decoder, &mut out_writer)?; } + #[cfg(feature = "deflate")] + CompressionEncoding::Deflate => { + let mut deflate_decoder = ZlibDecoder::new(&compressed_buf[0..len]); + std::io::copy(&mut deflate_decoder, &mut out_writer)?; + } #[cfg(feature = "zstd")] CompressionEncoding::Zstd => { let mut zstd_decoder = Decoder::new(&compressed_buf[0..len])?; @@ -282,7 +311,7 @@ pub enum SingleMessageCompressionOverride { #[cfg(test)] mod tests { - #[cfg(any(feature = "gzip", feature = "zstd"))] + #[cfg(any(feature = "gzip", feature = "deflate", feature = "zstd"))] use http::HeaderValue; use super::*; @@ -300,13 +329,13 @@ mod tests { const GZIP: HeaderValue = HeaderValue::from_static("gzip,identity"); let encodings = EnabledCompressionEncodings { - inner: [Some(CompressionEncoding::Gzip), None], + inner: [Some(CompressionEncoding::Gzip), None, None], }; assert_eq!(encodings.into_accept_encoding_header_value().unwrap(), GZIP); let encodings = EnabledCompressionEncodings { - inner: [None, Some(CompressionEncoding::Gzip)], + inner: [None, None, Some(CompressionEncoding::Gzip)], }; assert_eq!(encodings.into_accept_encoding_header_value().unwrap(), GZIP); @@ -318,43 +347,45 @@ mod tests { const ZSTD: HeaderValue = HeaderValue::from_static("zstd,identity"); let encodings = EnabledCompressionEncodings { - inner: [Some(CompressionEncoding::Zstd), None], + inner: [Some(CompressionEncoding::Zstd), None, None], }; assert_eq!(encodings.into_accept_encoding_header_value().unwrap(), ZSTD); let encodings = EnabledCompressionEncodings { - inner: [None, Some(CompressionEncoding::Zstd)], + inner: [None, None, Some(CompressionEncoding::Zstd)], }; assert_eq!(encodings.into_accept_encoding_header_value().unwrap(), ZSTD); } #[test] - #[cfg(all(feature = "gzip", feature = "zstd"))] - fn convert_gzip_and_zstd_into_header_value() { + #[cfg(all(feature = "gzip", feature = "deflate", feature = "zstd"))] + fn convert_compression_encodings_into_header_value() { let encodings = EnabledCompressionEncodings { inner: [ Some(CompressionEncoding::Gzip), + Some(CompressionEncoding::Deflate), Some(CompressionEncoding::Zstd), ], }; assert_eq!( encodings.into_accept_encoding_header_value().unwrap(), - HeaderValue::from_static("gzip,zstd,identity"), + HeaderValue::from_static("gzip,deflate,zstd,identity"), ); let encodings = EnabledCompressionEncodings { inner: [ Some(CompressionEncoding::Zstd), + Some(CompressionEncoding::Deflate), Some(CompressionEncoding::Gzip), ], }; assert_eq!( encodings.into_accept_encoding_header_value().unwrap(), - HeaderValue::from_static("zstd,gzip,identity"), + HeaderValue::from_static("zstd,deflate,gzip,identity"), ); } } diff --git a/tonic/src/lib.rs b/tonic/src/lib.rs index 6cdc41027..3dcbd2108 100644 --- a/tonic/src/lib.rs +++ b/tonic/src/lib.rs @@ -35,6 +35,8 @@ //! - `prost`: Enables the [`prost`] based gRPC [`Codec`] implementation. Enabled by default. //! - `gzip`: Enables compressing requests, responses, and streams. Depends on [`flate2`]. //! Not enabled by default. +//! - `deflate`: Enables compressing requests, responses, and streams. Depends on [`flate2`]. +//! Not enabled by default. //! - `zstd`: Enables compressing requests, responses, and streams. Depends on [`zstd`]. //! Not enabled by default. //! diff --git a/tonic/src/response.rs b/tonic/src/response.rs index 1089cdda4..e22e25eaf 100644 --- a/tonic/src/response.rs +++ b/tonic/src/response.rs @@ -120,7 +120,7 @@ impl Response { /// **Note**: This only has effect on responses to unary requests and responses to client to /// server streams. Response streams (server to client stream and bidirectional streams) will /// still be compressed according to the configuration of the server. - #[cfg(feature = "gzip")] + #[cfg(any(feature = "gzip", feature = "deflate", feature = "zstd"))] pub fn disable_compression(&mut self) { self.extensions_mut() .insert(crate::codec::compression::SingleMessageCompressionOverride::Disable); diff --git a/tonic/src/server/grpc.rs b/tonic/src/server/grpc.rs index 81a048222..0b057fcfb 100644 --- a/tonic/src/server/grpc.rs +++ b/tonic/src/server/grpc.rs @@ -435,7 +435,7 @@ where .headers .insert(http::header::CONTENT_TYPE, GRPC_CONTENT_TYPE); - #[cfg(any(feature = "gzip", feature = "zstd"))] + #[cfg(any(feature = "gzip", feature = "deflate", feature = "zstd"))] if let Some(encoding) = accept_encoding { // Set the content encoding parts.headers.insert(