feat(codec): compression support (#692)
* Initial compression support * Support configuring compression on `Server` * Minor clean up * Test that compression is actually happening * Clean up some todos * channels compressing requests * Move compression to be on the codecs * Test sending compressed request to server that doesn't support it * Clean up a bit * Compress server streams * Compress client streams * Bidirectional streaming compression * Handle receiving unsupported encoding * Clean up * Add note to future self * Support disabling compression for individual responses * Add docs * Add compression examples * Disable compression behind feature flag * Add some docs * Make flate2 optional dependency * Fix docs wording * Format * Reply with which encodings are supported * Convert tests to use mocked io * Fix lints * Use separate counters * Don't make a long stream * Address review feedback
This commit is contained in:
@@ -22,6 +22,7 @@ members = [
|
|||||||
"tests/integration_tests",
|
"tests/integration_tests",
|
||||||
"tests/stream_conflict",
|
"tests/stream_conflict",
|
||||||
"tests/root-crate-path",
|
"tests/root-crate-path",
|
||||||
|
"tests/compression",
|
||||||
"tonic-web/tests/integration"
|
"tonic-web/tests/integration"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -150,6 +150,14 @@ path = "src/hyper_warp_multiplex/client.rs"
|
|||||||
name = "hyper-warp-multiplex-server"
|
name = "hyper-warp-multiplex-server"
|
||||||
path = "src/hyper_warp_multiplex/server.rs"
|
path = "src/hyper_warp_multiplex/server.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "compression-server"
|
||||||
|
path = "src/compression/server.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "compression-client"
|
||||||
|
path = "src/compression/client.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tonic = { path = "../tonic", features = ["tls"] }
|
tonic = { path = "../tonic", features = ["tls"] }
|
||||||
prost = "0.7"
|
prost = "0.7"
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
use hello_world::greeter_client::GreeterClient;
|
||||||
|
use hello_world::HelloRequest;
|
||||||
|
use tonic::transport::Channel;
|
||||||
|
|
||||||
|
pub mod hello_world {
|
||||||
|
tonic::include_proto!("helloworld");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let channel = Channel::builder("http://[::1]:50051".parse().unwrap())
|
||||||
|
.connect()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut client = GreeterClient::new(channel).send_gzip().accept_gzip();
|
||||||
|
|
||||||
|
let request = tonic::Request::new(HelloRequest {
|
||||||
|
name: "Tonic".into(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let response = client.say_hello(request).await?;
|
||||||
|
|
||||||
|
dbg!(response);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
use tonic::{transport::Server, Request, Response, Status};
|
||||||
|
|
||||||
|
use hello_world::greeter_server::{Greeter, GreeterServer};
|
||||||
|
use hello_world::{HelloReply, HelloRequest};
|
||||||
|
|
||||||
|
pub mod hello_world {
|
||||||
|
tonic::include_proto!("helloworld");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct MyGreeter {}
|
||||||
|
|
||||||
|
#[tonic::async_trait]
|
||||||
|
impl Greeter for MyGreeter {
|
||||||
|
async fn say_hello(
|
||||||
|
&self,
|
||||||
|
request: Request<HelloRequest>,
|
||||||
|
) -> Result<Response<HelloReply>, Status> {
|
||||||
|
println!("Got a request from {:?}", request.remote_addr());
|
||||||
|
|
||||||
|
let reply = hello_world::HelloReply {
|
||||||
|
message: format!("Hello {}!", request.into_inner().name),
|
||||||
|
};
|
||||||
|
Ok(Response::new(reply))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let addr = "[::1]:50051".parse().unwrap();
|
||||||
|
let greeter = MyGreeter::default();
|
||||||
|
|
||||||
|
println!("GreeterServer listening on {}", addr);
|
||||||
|
|
||||||
|
let service = GreeterServer::new(greeter).send_gzip().accept_gzip();
|
||||||
|
|
||||||
|
Server::builder().add_service(service).serve(addr).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
[package]
|
||||||
|
name = "compression"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Lucio Franco <[email protected]>"]
|
||||||
|
edition = "2018"
|
||||||
|
publish = false
|
||||||
|
license = "MIT"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tonic = { path = "../../tonic", features = ["compression"] }
|
||||||
|
prost = "0.7"
|
||||||
|
tokio = { version = "1.0", features = ["macros", "rt-multi-thread", "net"] }
|
||||||
|
tower = { version = "0.4", features = [] }
|
||||||
|
http-body = "0.4"
|
||||||
|
http = "0.2"
|
||||||
|
tokio-stream = { version = "0.1.5", features = ["net"] }
|
||||||
|
tower-http = { version = "0.1", features = ["map-response-body", "map-request-body"] }
|
||||||
|
bytes = "1"
|
||||||
|
futures = "0.3"
|
||||||
|
pin-project = "1.0"
|
||||||
|
hyper = "0.14"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
tonic-build = { path = "../../tonic-build", features = ["compression"] }
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fn main() {
|
||||||
|
tonic_build::compile_protos("proto/test.proto").unwrap();
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package test;
|
||||||
|
|
||||||
|
import "google/protobuf/empty.proto";
|
||||||
|
|
||||||
|
service Test {
|
||||||
|
rpc CompressOutputUnary(google.protobuf.Empty) returns (SomeData);
|
||||||
|
rpc CompressInputUnary(SomeData) returns (google.protobuf.Empty);
|
||||||
|
rpc CompressOutputServerStream(google.protobuf.Empty) returns (stream SomeData);
|
||||||
|
rpc CompressInputClientStream(stream SomeData) returns (google.protobuf.Empty);
|
||||||
|
rpc CompressOutputClientStream(stream SomeData) returns (SomeData);
|
||||||
|
rpc CompressInputOutputBidirectionalStream(stream SomeData) returns (stream SomeData);
|
||||||
|
}
|
||||||
|
|
||||||
|
message SomeData {
|
||||||
|
// include a bunch of data so there actually is something to compress
|
||||||
|
bytes data = 1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn client_enabled_server_enabled() {
|
||||||
|
let (client, server) = tokio::io::duplex(UNCOMPRESSED_MIN_BODY_SIZE * 10);
|
||||||
|
|
||||||
|
let svc = test_server::TestServer::new(Svc::default())
|
||||||
|
.accept_gzip()
|
||||||
|
.send_gzip();
|
||||||
|
|
||||||
|
let request_bytes_counter = Arc::new(AtomicUsize::new(0));
|
||||||
|
let response_bytes_counter = Arc::new(AtomicUsize::new(0));
|
||||||
|
|
||||||
|
fn assert_right_encoding<B>(req: http::Request<B>) -> http::Request<B> {
|
||||||
|
assert_eq!(req.headers().get("grpc-encoding").unwrap(), "gzip");
|
||||||
|
req
|
||||||
|
}
|
||||||
|
|
||||||
|
tokio::spawn({
|
||||||
|
let request_bytes_counter = request_bytes_counter.clone();
|
||||||
|
let response_bytes_counter = response_bytes_counter.clone();
|
||||||
|
async move {
|
||||||
|
Server::builder()
|
||||||
|
.layer(
|
||||||
|
ServiceBuilder::new()
|
||||||
|
.map_request(assert_right_encoding)
|
||||||
|
.layer(measure_request_body_size_layer(
|
||||||
|
request_bytes_counter.clone(),
|
||||||
|
))
|
||||||
|
.layer(MapResponseBodyLayer::new(move |body| {
|
||||||
|
util::CountBytesBody {
|
||||||
|
inner: body,
|
||||||
|
counter: response_bytes_counter.clone(),
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.into_inner(),
|
||||||
|
)
|
||||||
|
.add_service(svc)
|
||||||
|
.serve_with_incoming(futures::stream::iter(vec![Ok::<_, std::io::Error>(
|
||||||
|
MockStream(server),
|
||||||
|
)]))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut client = test_client::TestClient::new(mock_io_channel(client).await)
|
||||||
|
.send_gzip()
|
||||||
|
.accept_gzip();
|
||||||
|
|
||||||
|
let data = [0_u8; UNCOMPRESSED_MIN_BODY_SIZE].to_vec();
|
||||||
|
let stream = futures::stream::iter(vec![SomeData { data: data.clone() }, SomeData { data }]);
|
||||||
|
let req = Request::new(stream);
|
||||||
|
|
||||||
|
let res = client
|
||||||
|
.compress_input_output_bidirectional_stream(req)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(res.metadata().get("grpc-encoding").unwrap(), "gzip");
|
||||||
|
|
||||||
|
let mut stream: Streaming<SomeData> = res.into_inner();
|
||||||
|
|
||||||
|
stream
|
||||||
|
.next()
|
||||||
|
.await
|
||||||
|
.expect("stream empty")
|
||||||
|
.expect("item was error");
|
||||||
|
|
||||||
|
stream
|
||||||
|
.next()
|
||||||
|
.await
|
||||||
|
.expect("stream empty")
|
||||||
|
.expect("item was error");
|
||||||
|
|
||||||
|
assert!(request_bytes_counter.load(SeqCst) < UNCOMPRESSED_MIN_BODY_SIZE);
|
||||||
|
assert!(response_bytes_counter.load(SeqCst) < UNCOMPRESSED_MIN_BODY_SIZE);
|
||||||
|
}
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
use super::*;
|
||||||
|
use http_body::Body as _;
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn client_enabled_server_enabled() {
|
||||||
|
let (client, server) = tokio::io::duplex(UNCOMPRESSED_MIN_BODY_SIZE * 10);
|
||||||
|
|
||||||
|
let svc = test_server::TestServer::new(Svc::default()).accept_gzip();
|
||||||
|
|
||||||
|
let request_bytes_counter = Arc::new(AtomicUsize::new(0));
|
||||||
|
|
||||||
|
fn assert_right_encoding<B>(req: http::Request<B>) -> http::Request<B> {
|
||||||
|
assert_eq!(req.headers().get("grpc-encoding").unwrap(), "gzip");
|
||||||
|
req
|
||||||
|
}
|
||||||
|
|
||||||
|
tokio::spawn({
|
||||||
|
let request_bytes_counter = request_bytes_counter.clone();
|
||||||
|
async move {
|
||||||
|
Server::builder()
|
||||||
|
.layer(
|
||||||
|
ServiceBuilder::new()
|
||||||
|
.map_request(assert_right_encoding)
|
||||||
|
.layer(measure_request_body_size_layer(
|
||||||
|
request_bytes_counter.clone(),
|
||||||
|
))
|
||||||
|
.into_inner(),
|
||||||
|
)
|
||||||
|
.add_service(svc)
|
||||||
|
.serve_with_incoming(futures::stream::iter(vec![Ok::<_, std::io::Error>(
|
||||||
|
MockStream(server),
|
||||||
|
)]))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut client = test_client::TestClient::new(mock_io_channel(client).await).send_gzip();
|
||||||
|
|
||||||
|
let data = [0_u8; UNCOMPRESSED_MIN_BODY_SIZE].to_vec();
|
||||||
|
let stream = futures::stream::iter(vec![SomeData { data: data.clone() }, SomeData { data }]);
|
||||||
|
let req = Request::new(Box::pin(stream));
|
||||||
|
|
||||||
|
client.compress_input_client_stream(req).await.unwrap();
|
||||||
|
|
||||||
|
let bytes_sent = request_bytes_counter.load(SeqCst);
|
||||||
|
assert!(bytes_sent < UNCOMPRESSED_MIN_BODY_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn client_disabled_server_enabled() {
|
||||||
|
let (client, server) = tokio::io::duplex(UNCOMPRESSED_MIN_BODY_SIZE * 10);
|
||||||
|
|
||||||
|
let svc = test_server::TestServer::new(Svc::default()).accept_gzip();
|
||||||
|
|
||||||
|
let request_bytes_counter = Arc::new(AtomicUsize::new(0));
|
||||||
|
|
||||||
|
fn assert_right_encoding<B>(req: http::Request<B>) -> http::Request<B> {
|
||||||
|
assert!(req.headers().get("grpc-encoding").is_none());
|
||||||
|
req
|
||||||
|
}
|
||||||
|
|
||||||
|
tokio::spawn({
|
||||||
|
let request_bytes_counter = request_bytes_counter.clone();
|
||||||
|
async move {
|
||||||
|
Server::builder()
|
||||||
|
.layer(
|
||||||
|
ServiceBuilder::new()
|
||||||
|
.map_request(assert_right_encoding)
|
||||||
|
.layer(measure_request_body_size_layer(
|
||||||
|
request_bytes_counter.clone(),
|
||||||
|
))
|
||||||
|
.into_inner(),
|
||||||
|
)
|
||||||
|
.add_service(svc)
|
||||||
|
.serve_with_incoming(futures::stream::iter(vec![Ok::<_, std::io::Error>(
|
||||||
|
MockStream(server),
|
||||||
|
)]))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut client = test_client::TestClient::new(mock_io_channel(client).await);
|
||||||
|
|
||||||
|
let data = [0_u8; UNCOMPRESSED_MIN_BODY_SIZE].to_vec();
|
||||||
|
let stream = futures::stream::iter(vec![SomeData { data: data.clone() }, SomeData { data }]);
|
||||||
|
let req = Request::new(Box::pin(stream));
|
||||||
|
|
||||||
|
client.compress_input_client_stream(req).await.unwrap();
|
||||||
|
|
||||||
|
let bytes_sent = request_bytes_counter.load(SeqCst);
|
||||||
|
assert!(bytes_sent > UNCOMPRESSED_MIN_BODY_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn client_enabled_server_disabled() {
|
||||||
|
let (client, server) = tokio::io::duplex(UNCOMPRESSED_MIN_BODY_SIZE * 10);
|
||||||
|
|
||||||
|
let svc = test_server::TestServer::new(Svc::default());
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
Server::builder()
|
||||||
|
.add_service(svc)
|
||||||
|
.serve_with_incoming(futures::stream::iter(vec![Ok::<_, std::io::Error>(
|
||||||
|
MockStream(server),
|
||||||
|
)]))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut client = test_client::TestClient::new(mock_io_channel(client).await).send_gzip();
|
||||||
|
|
||||||
|
let data = [0_u8; UNCOMPRESSED_MIN_BODY_SIZE].to_vec();
|
||||||
|
let stream = futures::stream::iter(vec![SomeData { data: data.clone() }, SomeData { data }]);
|
||||||
|
let req = Request::new(Box::pin(stream));
|
||||||
|
|
||||||
|
let status = client.compress_input_client_stream(req).await.unwrap_err();
|
||||||
|
|
||||||
|
assert_eq!(status.code(), tonic::Code::Unimplemented);
|
||||||
|
assert_eq!(
|
||||||
|
status.message(),
|
||||||
|
"Content is compressed with `gzip` which isn't supported"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn compressing_response_from_client_stream() {
|
||||||
|
let (client, server) = tokio::io::duplex(UNCOMPRESSED_MIN_BODY_SIZE * 10);
|
||||||
|
|
||||||
|
let svc = test_server::TestServer::new(Svc::default()).send_gzip();
|
||||||
|
|
||||||
|
let response_bytes_counter = Arc::new(AtomicUsize::new(0));
|
||||||
|
|
||||||
|
tokio::spawn({
|
||||||
|
let response_bytes_counter = response_bytes_counter.clone();
|
||||||
|
async move {
|
||||||
|
Server::builder()
|
||||||
|
.layer(
|
||||||
|
ServiceBuilder::new()
|
||||||
|
.layer(MapResponseBodyLayer::new(move |body| {
|
||||||
|
util::CountBytesBody {
|
||||||
|
inner: body,
|
||||||
|
counter: response_bytes_counter.clone(),
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.into_inner(),
|
||||||
|
)
|
||||||
|
.add_service(svc)
|
||||||
|
.serve_with_incoming(futures::stream::iter(vec![Ok::<_, std::io::Error>(
|
||||||
|
MockStream(server),
|
||||||
|
)]))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut client = test_client::TestClient::new(mock_io_channel(client).await).accept_gzip();
|
||||||
|
|
||||||
|
let stream = futures::stream::iter(vec![]);
|
||||||
|
let req = Request::new(Box::pin(stream));
|
||||||
|
|
||||||
|
let res = client.compress_output_client_stream(req).await.unwrap();
|
||||||
|
assert_eq!(res.metadata().get("grpc-encoding").unwrap(), "gzip");
|
||||||
|
let bytes_sent = response_bytes_counter.load(SeqCst);
|
||||||
|
assert!(bytes_sent < UNCOMPRESSED_MIN_BODY_SIZE);
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
use super::*;
|
||||||
|
use http_body::Body as _;
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn client_enabled_server_enabled() {
|
||||||
|
let (client, server) = tokio::io::duplex(UNCOMPRESSED_MIN_BODY_SIZE * 10);
|
||||||
|
|
||||||
|
let svc = test_server::TestServer::new(Svc::default()).accept_gzip();
|
||||||
|
|
||||||
|
let request_bytes_counter = Arc::new(AtomicUsize::new(0));
|
||||||
|
|
||||||
|
fn assert_right_encoding<B>(req: http::Request<B>) -> http::Request<B> {
|
||||||
|
assert_eq!(req.headers().get("grpc-encoding").unwrap(), "gzip");
|
||||||
|
req
|
||||||
|
}
|
||||||
|
|
||||||
|
tokio::spawn({
|
||||||
|
let request_bytes_counter = request_bytes_counter.clone();
|
||||||
|
async move {
|
||||||
|
Server::builder()
|
||||||
|
.layer(
|
||||||
|
ServiceBuilder::new()
|
||||||
|
.layer(
|
||||||
|
ServiceBuilder::new()
|
||||||
|
.map_request(assert_right_encoding)
|
||||||
|
.layer(measure_request_body_size_layer(request_bytes_counter))
|
||||||
|
.into_inner(),
|
||||||
|
)
|
||||||
|
.into_inner(),
|
||||||
|
)
|
||||||
|
.add_service(svc)
|
||||||
|
.serve_with_incoming(futures::stream::iter(vec![Ok::<_, std::io::Error>(
|
||||||
|
MockStream(server),
|
||||||
|
)]))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut client = test_client::TestClient::new(mock_io_channel(client).await).send_gzip();
|
||||||
|
|
||||||
|
for _ in 0..3 {
|
||||||
|
client
|
||||||
|
.compress_input_unary(SomeData {
|
||||||
|
data: [0_u8; UNCOMPRESSED_MIN_BODY_SIZE].to_vec(),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let bytes_sent = request_bytes_counter.load(SeqCst);
|
||||||
|
assert!(bytes_sent < UNCOMPRESSED_MIN_BODY_SIZE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn client_enabled_server_disabled() {
|
||||||
|
let (client, server) = tokio::io::duplex(UNCOMPRESSED_MIN_BODY_SIZE * 10);
|
||||||
|
|
||||||
|
let svc = test_server::TestServer::new(Svc::default());
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
Server::builder()
|
||||||
|
.add_service(svc)
|
||||||
|
.serve_with_incoming(futures::stream::iter(vec![Ok::<_, std::io::Error>(
|
||||||
|
MockStream(server),
|
||||||
|
)]))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut client = test_client::TestClient::new(mock_io_channel(client).await).send_gzip();
|
||||||
|
|
||||||
|
let status = client
|
||||||
|
.compress_input_unary(SomeData {
|
||||||
|
data: [0_u8; UNCOMPRESSED_MIN_BODY_SIZE].to_vec(),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap_err();
|
||||||
|
|
||||||
|
assert_eq!(status.code(), tonic::Code::Unimplemented);
|
||||||
|
assert_eq!(
|
||||||
|
status.message(),
|
||||||
|
"Content is compressed with `gzip` which isn't supported"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
status.metadata().get("grpc-accept-encoding").unwrap(),
|
||||||
|
"identity"
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,360 @@
|
|||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn client_enabled_server_enabled() {
|
||||||
|
let (client, server) = tokio::io::duplex(UNCOMPRESSED_MIN_BODY_SIZE * 10);
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct AssertCorrectAcceptEncoding<S>(S);
|
||||||
|
|
||||||
|
impl<S, B> Service<http::Request<B>> for AssertCorrectAcceptEncoding<S>
|
||||||
|
where
|
||||||
|
S: Service<http::Request<B>>,
|
||||||
|
{
|
||||||
|
type Response = S::Response;
|
||||||
|
type Error = S::Error;
|
||||||
|
type Future = S::Future;
|
||||||
|
|
||||||
|
fn poll_ready(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
) -> std::task::Poll<Result<(), Self::Error>> {
|
||||||
|
self.0.poll_ready(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, req: http::Request<B>) -> Self::Future {
|
||||||
|
assert_eq!(
|
||||||
|
req.headers().get("grpc-accept-encoding").unwrap(),
|
||||||
|
"gzip,identity"
|
||||||
|
);
|
||||||
|
self.0.call(req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let svc = test_server::TestServer::new(Svc::default()).send_gzip();
|
||||||
|
|
||||||
|
let response_bytes_counter = Arc::new(AtomicUsize::new(0));
|
||||||
|
|
||||||
|
tokio::spawn({
|
||||||
|
let response_bytes_counter = response_bytes_counter.clone();
|
||||||
|
async move {
|
||||||
|
Server::builder()
|
||||||
|
.layer(
|
||||||
|
ServiceBuilder::new()
|
||||||
|
.layer(layer_fn(AssertCorrectAcceptEncoding))
|
||||||
|
.layer(MapResponseBodyLayer::new(move |body| {
|
||||||
|
util::CountBytesBody {
|
||||||
|
inner: body,
|
||||||
|
counter: response_bytes_counter.clone(),
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.into_inner(),
|
||||||
|
)
|
||||||
|
.add_service(svc)
|
||||||
|
.serve_with_incoming(futures::stream::iter(vec![Ok::<_, std::io::Error>(
|
||||||
|
MockStream(server),
|
||||||
|
)]))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut client = test_client::TestClient::new(mock_io_channel(client).await).accept_gzip();
|
||||||
|
|
||||||
|
for _ in 0..3 {
|
||||||
|
let res = client.compress_output_unary(()).await.unwrap();
|
||||||
|
assert_eq!(res.metadata().get("grpc-encoding").unwrap(), "gzip");
|
||||||
|
let bytes_sent = response_bytes_counter.load(SeqCst);
|
||||||
|
assert!(bytes_sent < UNCOMPRESSED_MIN_BODY_SIZE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn client_enabled_server_disabled() {
|
||||||
|
let (client, server) = tokio::io::duplex(UNCOMPRESSED_MIN_BODY_SIZE * 10);
|
||||||
|
|
||||||
|
let svc = test_server::TestServer::new(Svc::default());
|
||||||
|
|
||||||
|
let response_bytes_counter = Arc::new(AtomicUsize::new(0));
|
||||||
|
|
||||||
|
tokio::spawn({
|
||||||
|
let response_bytes_counter = response_bytes_counter.clone();
|
||||||
|
async move {
|
||||||
|
Server::builder()
|
||||||
|
// no compression enable on the server so responses should not be compressed
|
||||||
|
.layer(
|
||||||
|
ServiceBuilder::new()
|
||||||
|
.layer(MapResponseBodyLayer::new(move |body| {
|
||||||
|
util::CountBytesBody {
|
||||||
|
inner: body,
|
||||||
|
counter: response_bytes_counter.clone(),
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.into_inner(),
|
||||||
|
)
|
||||||
|
.add_service(svc)
|
||||||
|
.serve_with_incoming(futures::stream::iter(vec![Ok::<_, std::io::Error>(
|
||||||
|
MockStream(server),
|
||||||
|
)]))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut client = test_client::TestClient::new(mock_io_channel(client).await).accept_gzip();
|
||||||
|
|
||||||
|
let res = client.compress_output_unary(()).await.unwrap();
|
||||||
|
|
||||||
|
assert!(res.metadata().get("grpc-encoding").is_none());
|
||||||
|
|
||||||
|
let bytes_sent = response_bytes_counter.load(SeqCst);
|
||||||
|
assert!(bytes_sent > UNCOMPRESSED_MIN_BODY_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn client_disabled() {
|
||||||
|
let (client, server) = tokio::io::duplex(UNCOMPRESSED_MIN_BODY_SIZE * 10);
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct AssertCorrectAcceptEncoding<S>(S);
|
||||||
|
|
||||||
|
impl<S, B> Service<http::Request<B>> for AssertCorrectAcceptEncoding<S>
|
||||||
|
where
|
||||||
|
S: Service<http::Request<B>>,
|
||||||
|
{
|
||||||
|
type Response = S::Response;
|
||||||
|
type Error = S::Error;
|
||||||
|
type Future = S::Future;
|
||||||
|
|
||||||
|
fn poll_ready(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
) -> std::task::Poll<Result<(), Self::Error>> {
|
||||||
|
self.0.poll_ready(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, req: http::Request<B>) -> Self::Future {
|
||||||
|
assert!(req.headers().get("grpc-accept-encoding").is_none());
|
||||||
|
self.0.call(req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let svc = test_server::TestServer::new(Svc::default()).send_gzip();
|
||||||
|
|
||||||
|
let response_bytes_counter = Arc::new(AtomicUsize::new(0));
|
||||||
|
|
||||||
|
tokio::spawn({
|
||||||
|
let response_bytes_counter = response_bytes_counter.clone();
|
||||||
|
async move {
|
||||||
|
Server::builder()
|
||||||
|
.layer(
|
||||||
|
ServiceBuilder::new()
|
||||||
|
.layer(layer_fn(AssertCorrectAcceptEncoding))
|
||||||
|
.layer(MapResponseBodyLayer::new(move |body| {
|
||||||
|
util::CountBytesBody {
|
||||||
|
inner: body,
|
||||||
|
counter: response_bytes_counter.clone(),
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.into_inner(),
|
||||||
|
)
|
||||||
|
.add_service(svc)
|
||||||
|
.serve_with_incoming(futures::stream::iter(vec![Ok::<_, std::io::Error>(
|
||||||
|
MockStream(server),
|
||||||
|
)]))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut client = test_client::TestClient::new(mock_io_channel(client).await);
|
||||||
|
|
||||||
|
let res = client.compress_output_unary(()).await.unwrap();
|
||||||
|
|
||||||
|
assert!(res.metadata().get("grpc-encoding").is_none());
|
||||||
|
|
||||||
|
let bytes_sent = response_bytes_counter.load(SeqCst);
|
||||||
|
assert!(bytes_sent > UNCOMPRESSED_MIN_BODY_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn server_replying_with_unsupported_encoding() {
|
||||||
|
let (client, server) = tokio::io::duplex(UNCOMPRESSED_MIN_BODY_SIZE * 10);
|
||||||
|
|
||||||
|
let svc = test_server::TestServer::new(Svc::default()).send_gzip();
|
||||||
|
|
||||||
|
fn add_weird_content_encoding<B>(mut response: http::Response<B>) -> http::Response<B> {
|
||||||
|
response
|
||||||
|
.headers_mut()
|
||||||
|
.insert("grpc-encoding", "br".parse().unwrap());
|
||||||
|
response
|
||||||
|
}
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
Server::builder()
|
||||||
|
.layer(
|
||||||
|
ServiceBuilder::new()
|
||||||
|
.map_response(add_weird_content_encoding)
|
||||||
|
.into_inner(),
|
||||||
|
)
|
||||||
|
.add_service(svc)
|
||||||
|
.serve_with_incoming(futures::stream::iter(vec![Ok::<_, std::io::Error>(
|
||||||
|
MockStream(server),
|
||||||
|
)]))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut client = test_client::TestClient::new(mock_io_channel(client).await).accept_gzip();
|
||||||
|
let status: Status = client.compress_output_unary(()).await.unwrap_err();
|
||||||
|
|
||||||
|
assert_eq!(status.code(), tonic::Code::Unimplemented);
|
||||||
|
assert_eq!(
|
||||||
|
status.message(),
|
||||||
|
"Content is compressed with `br` which isn't supported"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn disabling_compression_on_single_response() {
|
||||||
|
let (client, server) = tokio::io::duplex(UNCOMPRESSED_MIN_BODY_SIZE * 10);
|
||||||
|
|
||||||
|
let svc = test_server::TestServer::new(Svc {
|
||||||
|
disable_compressing_on_response: true,
|
||||||
|
})
|
||||||
|
.send_gzip();
|
||||||
|
|
||||||
|
let response_bytes_counter = Arc::new(AtomicUsize::new(0));
|
||||||
|
|
||||||
|
tokio::spawn({
|
||||||
|
let response_bytes_counter = response_bytes_counter.clone();
|
||||||
|
async move {
|
||||||
|
Server::builder()
|
||||||
|
.layer(
|
||||||
|
ServiceBuilder::new()
|
||||||
|
.layer(MapResponseBodyLayer::new(move |body| {
|
||||||
|
util::CountBytesBody {
|
||||||
|
inner: body,
|
||||||
|
counter: response_bytes_counter.clone(),
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.into_inner(),
|
||||||
|
)
|
||||||
|
.add_service(svc)
|
||||||
|
.serve_with_incoming(futures::stream::iter(vec![Ok::<_, std::io::Error>(
|
||||||
|
MockStream(server),
|
||||||
|
)]))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut client = test_client::TestClient::new(mock_io_channel(client).await).accept_gzip();
|
||||||
|
|
||||||
|
let res = client.compress_output_unary(()).await.unwrap();
|
||||||
|
assert_eq!(res.metadata().get("grpc-encoding").unwrap(), "gzip");
|
||||||
|
let bytes_sent = response_bytes_counter.load(SeqCst);
|
||||||
|
assert!(bytes_sent > UNCOMPRESSED_MIN_BODY_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn disabling_compression_on_response_but_keeping_compression_on_stream() {
|
||||||
|
let (client, server) = tokio::io::duplex(UNCOMPRESSED_MIN_BODY_SIZE * 10);
|
||||||
|
|
||||||
|
let svc = test_server::TestServer::new(Svc {
|
||||||
|
disable_compressing_on_response: true,
|
||||||
|
})
|
||||||
|
.send_gzip();
|
||||||
|
|
||||||
|
let response_bytes_counter = Arc::new(AtomicUsize::new(0));
|
||||||
|
|
||||||
|
tokio::spawn({
|
||||||
|
let response_bytes_counter = response_bytes_counter.clone();
|
||||||
|
async move {
|
||||||
|
Server::builder()
|
||||||
|
.layer(
|
||||||
|
ServiceBuilder::new()
|
||||||
|
.layer(MapResponseBodyLayer::new(move |body| {
|
||||||
|
util::CountBytesBody {
|
||||||
|
inner: body,
|
||||||
|
counter: response_bytes_counter.clone(),
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.into_inner(),
|
||||||
|
)
|
||||||
|
.add_service(svc)
|
||||||
|
.serve_with_incoming(futures::stream::iter(vec![Ok::<_, std::io::Error>(
|
||||||
|
MockStream(server),
|
||||||
|
)]))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut client = test_client::TestClient::new(mock_io_channel(client).await).accept_gzip();
|
||||||
|
|
||||||
|
let res = client.compress_output_server_stream(()).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(res.metadata().get("grpc-encoding").unwrap(), "gzip");
|
||||||
|
|
||||||
|
let mut stream: Streaming<SomeData> = res.into_inner();
|
||||||
|
|
||||||
|
stream
|
||||||
|
.next()
|
||||||
|
.await
|
||||||
|
.expect("stream empty")
|
||||||
|
.expect("item was error");
|
||||||
|
assert!(response_bytes_counter.load(SeqCst) < UNCOMPRESSED_MIN_BODY_SIZE);
|
||||||
|
|
||||||
|
stream
|
||||||
|
.next()
|
||||||
|
.await
|
||||||
|
.expect("stream empty")
|
||||||
|
.expect("item was error");
|
||||||
|
assert!(response_bytes_counter.load(SeqCst) < UNCOMPRESSED_MIN_BODY_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn disabling_compression_on_response_from_client_stream() {
|
||||||
|
let (client, server) = tokio::io::duplex(UNCOMPRESSED_MIN_BODY_SIZE * 10);
|
||||||
|
|
||||||
|
let svc = test_server::TestServer::new(Svc {
|
||||||
|
disable_compressing_on_response: true,
|
||||||
|
})
|
||||||
|
.send_gzip();
|
||||||
|
|
||||||
|
let response_bytes_counter = Arc::new(AtomicUsize::new(0));
|
||||||
|
|
||||||
|
tokio::spawn({
|
||||||
|
let response_bytes_counter = response_bytes_counter.clone();
|
||||||
|
async move {
|
||||||
|
Server::builder()
|
||||||
|
.layer(
|
||||||
|
ServiceBuilder::new()
|
||||||
|
.layer(MapResponseBodyLayer::new(move |body| {
|
||||||
|
util::CountBytesBody {
|
||||||
|
inner: body,
|
||||||
|
counter: response_bytes_counter.clone(),
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.into_inner(),
|
||||||
|
)
|
||||||
|
.add_service(svc)
|
||||||
|
.serve_with_incoming(futures::stream::iter(vec![Ok::<_, std::io::Error>(
|
||||||
|
MockStream(server),
|
||||||
|
)]))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut client = test_client::TestClient::new(mock_io_channel(client).await).accept_gzip();
|
||||||
|
|
||||||
|
let stream = futures::stream::iter(vec![]);
|
||||||
|
let req = Request::new(Box::pin(stream));
|
||||||
|
|
||||||
|
let res = client.compress_output_client_stream(req).await.unwrap();
|
||||||
|
assert_eq!(res.metadata().get("grpc-encoding").unwrap(), "gzip");
|
||||||
|
let bytes_sent = response_bytes_counter.load(SeqCst);
|
||||||
|
assert!(bytes_sent > UNCOMPRESSED_MIN_BODY_SIZE);
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
#![allow(unused_imports)]
|
||||||
|
|
||||||
|
use self::util::*;
|
||||||
|
use crate::util::{mock_io_channel, MockStream};
|
||||||
|
use futures::{Stream, StreamExt};
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::{
|
||||||
|
pin::Pin,
|
||||||
|
sync::{
|
||||||
|
atomic::{AtomicUsize, Ordering::SeqCst},
|
||||||
|
Arc,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use tokio::net::TcpListener;
|
||||||
|
use tonic::{
|
||||||
|
transport::{Channel, Endpoint, Server, Uri},
|
||||||
|
Request, Response, Status, Streaming,
|
||||||
|
};
|
||||||
|
use tower::{layer::layer_fn, service_fn, Service, ServiceBuilder};
|
||||||
|
use tower_http::{map_request_body::MapRequestBodyLayer, map_response_body::MapResponseBodyLayer};
|
||||||
|
|
||||||
|
mod bidirectional_stream;
|
||||||
|
mod client_stream;
|
||||||
|
mod compressing_request;
|
||||||
|
mod compressing_response;
|
||||||
|
mod server_stream;
|
||||||
|
mod util;
|
||||||
|
|
||||||
|
tonic::include_proto!("test");
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Svc {
|
||||||
|
disable_compressing_on_response: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Svc {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
disable_compressing_on_response: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const UNCOMPRESSED_MIN_BODY_SIZE: usize = 1024;
|
||||||
|
|
||||||
|
impl Svc {
|
||||||
|
fn prepare_response<B>(&self, mut res: Response<B>) -> Response<B> {
|
||||||
|
if self.disable_compressing_on_response {
|
||||||
|
res.disable_compression();
|
||||||
|
}
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tonic::async_trait]
|
||||||
|
impl test_server::Test for Svc {
|
||||||
|
async fn compress_output_unary(&self, _req: Request<()>) -> Result<Response<SomeData>, Status> {
|
||||||
|
let data = [0_u8; UNCOMPRESSED_MIN_BODY_SIZE];
|
||||||
|
|
||||||
|
Ok(self.prepare_response(Response::new(SomeData {
|
||||||
|
data: data.to_vec(),
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn compress_input_unary(&self, req: Request<SomeData>) -> Result<Response<()>, Status> {
|
||||||
|
assert_eq!(req.into_inner().data.len(), UNCOMPRESSED_MIN_BODY_SIZE);
|
||||||
|
Ok(Response::new(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
type CompressOutputServerStreamStream =
|
||||||
|
Pin<Box<dyn Stream<Item = Result<SomeData, Status>> + Send + Sync + 'static>>;
|
||||||
|
|
||||||
|
async fn compress_output_server_stream(
|
||||||
|
&self,
|
||||||
|
_req: Request<()>,
|
||||||
|
) -> Result<Response<Self::CompressOutputServerStreamStream>, Status> {
|
||||||
|
let data = [0_u8; UNCOMPRESSED_MIN_BODY_SIZE].to_vec();
|
||||||
|
let stream = futures::stream::repeat(SomeData { data })
|
||||||
|
.take(2)
|
||||||
|
.map(Ok::<_, Status>);
|
||||||
|
Ok(self.prepare_response(Response::new(Box::pin(stream))))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn compress_input_client_stream(
|
||||||
|
&self,
|
||||||
|
req: Request<Streaming<SomeData>>,
|
||||||
|
) -> Result<Response<()>, Status> {
|
||||||
|
let mut stream = req.into_inner();
|
||||||
|
while let Some(item) = stream.next().await {
|
||||||
|
item.unwrap();
|
||||||
|
}
|
||||||
|
Ok(self.prepare_response(Response::new(())))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn compress_output_client_stream(
|
||||||
|
&self,
|
||||||
|
req: Request<Streaming<SomeData>>,
|
||||||
|
) -> Result<Response<SomeData>, Status> {
|
||||||
|
let mut stream = req.into_inner();
|
||||||
|
while let Some(item) = stream.next().await {
|
||||||
|
item.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = [0_u8; UNCOMPRESSED_MIN_BODY_SIZE];
|
||||||
|
|
||||||
|
Ok(self.prepare_response(Response::new(SomeData {
|
||||||
|
data: data.to_vec(),
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
type CompressInputOutputBidirectionalStreamStream =
|
||||||
|
Pin<Box<dyn Stream<Item = Result<SomeData, Status>> + Send + Sync + 'static>>;
|
||||||
|
|
||||||
|
async fn compress_input_output_bidirectional_stream(
|
||||||
|
&self,
|
||||||
|
req: Request<Streaming<SomeData>>,
|
||||||
|
) -> Result<Response<Self::CompressInputOutputBidirectionalStreamStream>, Status> {
|
||||||
|
let mut stream = req.into_inner();
|
||||||
|
while let Some(item) = stream.next().await {
|
||||||
|
item.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = [0_u8; UNCOMPRESSED_MIN_BODY_SIZE].to_vec();
|
||||||
|
let stream = futures::stream::repeat(SomeData { data })
|
||||||
|
.take(2)
|
||||||
|
.map(Ok::<_, Status>);
|
||||||
|
Ok(self.prepare_response(Response::new(Box::pin(stream))))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
use super::*;
|
||||||
|
use tonic::Streaming;
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn client_enabled_server_enabled() {
|
||||||
|
let (client, server) = tokio::io::duplex(UNCOMPRESSED_MIN_BODY_SIZE * 10);
|
||||||
|
|
||||||
|
let svc = test_server::TestServer::new(Svc::default()).send_gzip();
|
||||||
|
|
||||||
|
let response_bytes_counter = Arc::new(AtomicUsize::new(0));
|
||||||
|
|
||||||
|
tokio::spawn({
|
||||||
|
let response_bytes_counter = response_bytes_counter.clone();
|
||||||
|
async move {
|
||||||
|
Server::builder()
|
||||||
|
.layer(
|
||||||
|
ServiceBuilder::new()
|
||||||
|
.layer(MapResponseBodyLayer::new(move |body| {
|
||||||
|
util::CountBytesBody {
|
||||||
|
inner: body,
|
||||||
|
counter: response_bytes_counter.clone(),
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.into_inner(),
|
||||||
|
)
|
||||||
|
.add_service(svc)
|
||||||
|
.serve_with_incoming(futures::stream::iter(vec![Ok::<_, std::io::Error>(
|
||||||
|
MockStream(server),
|
||||||
|
)]))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut client = test_client::TestClient::new(mock_io_channel(client).await).accept_gzip();
|
||||||
|
|
||||||
|
let res = client.compress_output_server_stream(()).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(res.metadata().get("grpc-encoding").unwrap(), "gzip");
|
||||||
|
|
||||||
|
let mut stream: Streaming<SomeData> = res.into_inner();
|
||||||
|
|
||||||
|
stream
|
||||||
|
.next()
|
||||||
|
.await
|
||||||
|
.expect("stream empty")
|
||||||
|
.expect("item was error");
|
||||||
|
assert!(response_bytes_counter.load(SeqCst) < UNCOMPRESSED_MIN_BODY_SIZE);
|
||||||
|
|
||||||
|
stream
|
||||||
|
.next()
|
||||||
|
.await
|
||||||
|
.expect("stream empty")
|
||||||
|
.expect("item was error");
|
||||||
|
assert!(response_bytes_counter.load(SeqCst) < UNCOMPRESSED_MIN_BODY_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn client_disabled_server_enabled() {
|
||||||
|
let (client, server) = tokio::io::duplex(UNCOMPRESSED_MIN_BODY_SIZE * 10);
|
||||||
|
|
||||||
|
let svc = test_server::TestServer::new(Svc::default()).send_gzip();
|
||||||
|
|
||||||
|
let response_bytes_counter = Arc::new(AtomicUsize::new(0));
|
||||||
|
|
||||||
|
tokio::spawn({
|
||||||
|
let response_bytes_counter = response_bytes_counter.clone();
|
||||||
|
async move {
|
||||||
|
Server::builder()
|
||||||
|
.layer(
|
||||||
|
ServiceBuilder::new()
|
||||||
|
.layer(MapResponseBodyLayer::new(move |body| {
|
||||||
|
util::CountBytesBody {
|
||||||
|
inner: body,
|
||||||
|
counter: response_bytes_counter.clone(),
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.into_inner(),
|
||||||
|
)
|
||||||
|
.add_service(svc)
|
||||||
|
.serve_with_incoming(futures::stream::iter(vec![Ok::<_, std::io::Error>(
|
||||||
|
MockStream(server),
|
||||||
|
)]))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut client = test_client::TestClient::new(mock_io_channel(client).await);
|
||||||
|
|
||||||
|
let res = client.compress_output_server_stream(()).await.unwrap();
|
||||||
|
|
||||||
|
assert!(res.metadata().get("grpc-encoding").is_none());
|
||||||
|
|
||||||
|
let mut stream: Streaming<SomeData> = res.into_inner();
|
||||||
|
|
||||||
|
stream
|
||||||
|
.next()
|
||||||
|
.await
|
||||||
|
.expect("stream empty")
|
||||||
|
.expect("item was error");
|
||||||
|
assert!(response_bytes_counter.load(SeqCst) > UNCOMPRESSED_MIN_BODY_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn client_enabled_server_disabled() {
|
||||||
|
let (client, server) = tokio::io::duplex(UNCOMPRESSED_MIN_BODY_SIZE * 10);
|
||||||
|
|
||||||
|
let svc = test_server::TestServer::new(Svc::default());
|
||||||
|
|
||||||
|
let response_bytes_counter = Arc::new(AtomicUsize::new(0));
|
||||||
|
|
||||||
|
tokio::spawn({
|
||||||
|
let response_bytes_counter = response_bytes_counter.clone();
|
||||||
|
async move {
|
||||||
|
Server::builder()
|
||||||
|
.layer(
|
||||||
|
ServiceBuilder::new()
|
||||||
|
.layer(MapResponseBodyLayer::new(move |body| {
|
||||||
|
util::CountBytesBody {
|
||||||
|
inner: body,
|
||||||
|
counter: response_bytes_counter.clone(),
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.into_inner(),
|
||||||
|
)
|
||||||
|
.add_service(svc)
|
||||||
|
.serve_with_incoming(futures::stream::iter(vec![Ok::<_, std::io::Error>(
|
||||||
|
MockStream(server),
|
||||||
|
)]))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut client = test_client::TestClient::new(mock_io_channel(client).await).accept_gzip();
|
||||||
|
|
||||||
|
let res = client.compress_output_server_stream(()).await.unwrap();
|
||||||
|
|
||||||
|
assert!(res.metadata().get("grpc-encoding").is_none());
|
||||||
|
|
||||||
|
let mut stream: Streaming<SomeData> = res.into_inner();
|
||||||
|
|
||||||
|
stream
|
||||||
|
.next()
|
||||||
|
.await
|
||||||
|
.expect("stream empty")
|
||||||
|
.expect("item was error");
|
||||||
|
assert!(response_bytes_counter.load(SeqCst) > UNCOMPRESSED_MIN_BODY_SIZE);
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
use super::*;
|
||||||
|
use bytes::Bytes;
|
||||||
|
use futures::ready;
|
||||||
|
use http_body::Body;
|
||||||
|
use pin_project::pin_project;
|
||||||
|
use std::{
|
||||||
|
pin::Pin,
|
||||||
|
sync::{
|
||||||
|
atomic::{AtomicUsize, Ordering::SeqCst},
|
||||||
|
Arc,
|
||||||
|
},
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||||
|
use tonic::transport::{server::Connected, Channel};
|
||||||
|
use tower_http::map_request_body::MapRequestBodyLayer;
|
||||||
|
|
||||||
|
/// A body that tracks how many bytes passes through it
|
||||||
|
#[pin_project]
|
||||||
|
pub struct CountBytesBody<B> {
|
||||||
|
#[pin]
|
||||||
|
pub inner: B,
|
||||||
|
pub counter: Arc<AtomicUsize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B> Body for CountBytesBody<B>
|
||||||
|
where
|
||||||
|
B: Body<Data = Bytes>,
|
||||||
|
{
|
||||||
|
type Data = B::Data;
|
||||||
|
type Error = B::Error;
|
||||||
|
|
||||||
|
fn poll_data(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
) -> Poll<Option<Result<Self::Data, Self::Error>>> {
|
||||||
|
let this = self.project();
|
||||||
|
let counter: Arc<AtomicUsize> = this.counter.clone();
|
||||||
|
match ready!(this.inner.poll_data(cx)) {
|
||||||
|
Some(Ok(chunk)) => {
|
||||||
|
println!("response body chunk size = {}", chunk.len());
|
||||||
|
counter.fetch_add(chunk.len(), SeqCst);
|
||||||
|
Poll::Ready(Some(Ok(chunk)))
|
||||||
|
}
|
||||||
|
x => Poll::Ready(x),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_trailers(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
) -> Poll<Result<Option<http::HeaderMap>, Self::Error>> {
|
||||||
|
self.project().inner.poll_trailers(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_end_stream(&self) -> bool {
|
||||||
|
self.inner.is_end_stream()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size_hint(&self) -> http_body::SizeHint {
|
||||||
|
self.inner.size_hint()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn measure_request_body_size_layer(
|
||||||
|
bytes_sent_counter: Arc<AtomicUsize>,
|
||||||
|
) -> MapRequestBodyLayer<impl Fn(hyper::Body) -> hyper::Body + Clone> {
|
||||||
|
MapRequestBodyLayer::new(move |mut body: hyper::Body| {
|
||||||
|
let (mut tx, new_body) = hyper::Body::channel();
|
||||||
|
|
||||||
|
let bytes_sent_counter = bytes_sent_counter.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
while let Some(chunk) = body.data().await {
|
||||||
|
let chunk = chunk.unwrap();
|
||||||
|
println!("request body chunk size = {}", chunk.len());
|
||||||
|
bytes_sent_counter.fetch_add(chunk.len(), SeqCst);
|
||||||
|
tx.send_data(chunk).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(trailers) = body.trailers().await.unwrap() {
|
||||||
|
tx.send_trailers(trailers).await.unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
new_body
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct MockStream(pub tokio::io::DuplexStream);
|
||||||
|
|
||||||
|
impl Connected for MockStream {
|
||||||
|
type ConnectInfo = ();
|
||||||
|
|
||||||
|
fn connect_info(&self) -> Self::ConnectInfo {}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsyncRead for MockStream {
|
||||||
|
fn poll_read(
|
||||||
|
mut self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
buf: &mut ReadBuf<'_>,
|
||||||
|
) -> Poll<std::io::Result<()>> {
|
||||||
|
Pin::new(&mut self.0).poll_read(cx, buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsyncWrite for MockStream {
|
||||||
|
fn poll_write(
|
||||||
|
mut self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
buf: &[u8],
|
||||||
|
) -> Poll<std::io::Result<usize>> {
|
||||||
|
Pin::new(&mut self.0).poll_write(cx, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
|
||||||
|
Pin::new(&mut self.0).poll_flush(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
|
||||||
|
Pin::new(&mut self.0).poll_shutdown(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub async fn mock_io_channel(client: tokio::io::DuplexStream) -> Channel {
|
||||||
|
let mut client = Some(client);
|
||||||
|
|
||||||
|
Endpoint::try_from("http://[::]:50051")
|
||||||
|
.unwrap()
|
||||||
|
.connect_with_connector(service_fn(move |_: Uri| {
|
||||||
|
let client = client.take().unwrap();
|
||||||
|
async move { Ok::<_, std::io::Error>(MockStream(client)) }
|
||||||
|
}))
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
@@ -26,6 +26,7 @@ default = ["transport", "rustfmt", "prost"]
|
|||||||
rustfmt = []
|
rustfmt = []
|
||||||
transport = []
|
transport = []
|
||||||
prost = ["prost-build"]
|
prost = ["prost-build"]
|
||||||
|
compression = []
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
|
|||||||
+21
-21
@@ -20,8 +20,6 @@ pub fn generate<T: Service>(
|
|||||||
let connect = generate_connect(&service_ident);
|
let connect = generate_connect(&service_ident);
|
||||||
let service_doc = generate_doc_comments(service.comment());
|
let service_doc = generate_doc_comments(service.comment());
|
||||||
|
|
||||||
let struct_debug = format!("{} {{{{ ... }}}}", &service_ident);
|
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
/// Generated client implementations.
|
/// Generated client implementations.
|
||||||
pub mod #client_mod {
|
pub mod #client_mod {
|
||||||
@@ -29,6 +27,7 @@ pub fn generate<T: Service>(
|
|||||||
use tonic::codegen::*;
|
use tonic::codegen::*;
|
||||||
|
|
||||||
#service_doc
|
#service_doc
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct #service_ident<T> {
|
pub struct #service_ident<T> {
|
||||||
inner: tonic::client::Grpc<T>,
|
inner: tonic::client::Grpc<T>,
|
||||||
}
|
}
|
||||||
@@ -59,22 +58,23 @@ pub fn generate<T: Service>(
|
|||||||
#service_ident::new(InterceptedService::new(inner, interceptor))
|
#service_ident::new(InterceptedService::new(inner, interceptor))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Compress requests with `gzip`.
|
||||||
|
///
|
||||||
|
/// This requires the server to support it otherwise it might respond with an
|
||||||
|
/// error.
|
||||||
|
pub fn send_gzip(mut self) -> Self {
|
||||||
|
self.inner = self.inner.send_gzip();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable decompressing responses with `gzip`.
|
||||||
|
pub fn accept_gzip(mut self) -> Self {
|
||||||
|
self.inner = self.inner.accept_gzip();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
#methods
|
#methods
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Clone> Clone for #service_ident<T> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Self {
|
|
||||||
inner: self.inner.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> std::fmt::Debug for #service_ident<T> {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, #struct_debug)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,10 +153,10 @@ fn generate_unary<T: Method>(
|
|||||||
&mut self,
|
&mut self,
|
||||||
request: impl tonic::IntoRequest<#request>,
|
request: impl tonic::IntoRequest<#request>,
|
||||||
) -> Result<tonic::Response<#response>, tonic::Status> {
|
) -> Result<tonic::Response<#response>, tonic::Status> {
|
||||||
self.inner.ready().await.map_err(|e| {
|
self.inner.ready().await.map_err(|e| {
|
||||||
tonic::Status::new(tonic::Code::Unknown, format!("Service was not ready: {}", e.into()))
|
tonic::Status::new(tonic::Code::Unknown, format!("Service was not ready: {}", e.into()))
|
||||||
})?;
|
})?;
|
||||||
let codec = #codec_name::default();
|
let codec = #codec_name::default();
|
||||||
let path = http::uri::PathAndQuery::from_static(#path);
|
let path = http::uri::PathAndQuery::from_static(#path);
|
||||||
self.inner.unary(request.into_request(), path, codec).await
|
self.inner.unary(request.into_request(), path, codec).await
|
||||||
}
|
}
|
||||||
@@ -204,7 +204,7 @@ fn generate_client_streaming<T: Method>(
|
|||||||
pub async fn #ident(
|
pub async fn #ident(
|
||||||
&mut self,
|
&mut self,
|
||||||
request: impl tonic::IntoStreamingRequest<Message = #request>
|
request: impl tonic::IntoStreamingRequest<Message = #request>
|
||||||
) -> Result<tonic::Response<#response>, tonic::Status> {
|
) -> Result<tonic::Response<#response>, tonic::Status> where T: std::fmt::Debug {
|
||||||
self.inner.ready().await.map_err(|e| {
|
self.inner.ready().await.map_err(|e| {
|
||||||
tonic::Status::new(tonic::Code::Unknown, format!("Service was not ready: {}", e.into()))
|
tonic::Status::new(tonic::Code::Unknown, format!("Service was not ready: {}", e.into()))
|
||||||
})?;
|
})?;
|
||||||
|
|||||||
@@ -36,6 +36,32 @@ pub fn generate<T: Service>(
|
|||||||
);
|
);
|
||||||
let transport = generate_transport(&server_service, &server_trait, &path);
|
let transport = generate_transport(&server_service, &server_trait, &path);
|
||||||
|
|
||||||
|
let compression_enabled = cfg!(feature = "compression");
|
||||||
|
|
||||||
|
let compression_config_ty = if compression_enabled {
|
||||||
|
quote! { EnabledCompressionEncodings }
|
||||||
|
} else {
|
||||||
|
quote! { () }
|
||||||
|
};
|
||||||
|
|
||||||
|
let configure_compression_methods = if compression_enabled {
|
||||||
|
quote! {
|
||||||
|
/// Enable decompressing requests with `gzip`.
|
||||||
|
pub fn accept_gzip(mut self) -> Self {
|
||||||
|
self.accept_compression_encodings.enable_gzip();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compress responses with `gzip`, if the client supports it.
|
||||||
|
pub fn send_gzip(mut self) -> Self {
|
||||||
|
self.send_compression_encodings.enable_gzip();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {}
|
||||||
|
};
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
/// Generated server implementations.
|
/// Generated server implementations.
|
||||||
pub mod #server_mod {
|
pub mod #server_mod {
|
||||||
@@ -48,6 +74,8 @@ pub fn generate<T: Service>(
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct #server_service<T: #server_trait> {
|
pub struct #server_service<T: #server_trait> {
|
||||||
inner: _Inner<T>,
|
inner: _Inner<T>,
|
||||||
|
accept_compression_encodings: #compression_config_ty,
|
||||||
|
send_compression_encodings: #compression_config_ty,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct _Inner<T>(Arc<T>);
|
struct _Inner<T>(Arc<T>);
|
||||||
@@ -56,7 +84,11 @@ pub fn generate<T: Service>(
|
|||||||
pub fn new(inner: T) -> Self {
|
pub fn new(inner: T) -> Self {
|
||||||
let inner = Arc::new(inner);
|
let inner = Arc::new(inner);
|
||||||
let inner = _Inner(inner);
|
let inner = _Inner(inner);
|
||||||
Self { inner }
|
Self {
|
||||||
|
inner,
|
||||||
|
accept_compression_encodings: Default::default(),
|
||||||
|
send_compression_encodings: Default::default(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_interceptor<F>(inner: T, interceptor: F) -> InterceptedService<Self, F>
|
pub fn with_interceptor<F>(inner: T, interceptor: F) -> InterceptedService<Self, F>
|
||||||
@@ -65,6 +97,8 @@ pub fn generate<T: Service>(
|
|||||||
{
|
{
|
||||||
InterceptedService::new(Self::new(inner), interceptor)
|
InterceptedService::new(Self::new(inner), interceptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#configure_compression_methods
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, B> Service<http::Request<B>> for #server_service<T>
|
impl<T, B> Service<http::Request<B>> for #server_service<T>
|
||||||
@@ -102,7 +136,11 @@ pub fn generate<T: Service>(
|
|||||||
impl<T: #server_trait> Clone for #server_service<T> {
|
impl<T: #server_trait> Clone for #server_service<T> {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
let inner = self.inner.clone();
|
let inner = self.inner.clone();
|
||||||
Self { inner }
|
Self {
|
||||||
|
inner,
|
||||||
|
accept_compression_encodings: self.accept_compression_encodings,
|
||||||
|
send_compression_encodings: self.send_compression_encodings,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,13 +373,16 @@ fn generate_unary<T: Method>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let accept_compression_encodings = self.accept_compression_encodings;
|
||||||
|
let send_compression_encodings = self.send_compression_encodings;
|
||||||
let inner = self.inner.clone();
|
let inner = self.inner.clone();
|
||||||
let fut = async move {
|
let fut = async move {
|
||||||
let inner = inner.0;
|
let inner = inner.0;
|
||||||
let method = #service_ident(inner);
|
let method = #service_ident(inner);
|
||||||
let codec = #codec_name::default();
|
let codec = #codec_name::default();
|
||||||
|
|
||||||
let mut grpc = tonic::server::Grpc::new(codec);
|
let mut grpc = tonic::server::Grpc::new(codec)
|
||||||
|
.apply_compression_config(accept_compression_encodings, send_compression_encodings);
|
||||||
|
|
||||||
let res = grpc.unary(method, req).await;
|
let res = grpc.unary(method, req).await;
|
||||||
Ok(res)
|
Ok(res)
|
||||||
@@ -379,19 +420,21 @@ fn generate_server_streaming<T: Method>(
|
|||||||
let inner = self.0.clone();
|
let inner = self.0.clone();
|
||||||
let fut = async move {
|
let fut = async move {
|
||||||
(*inner).#method_ident(request).await
|
(*inner).#method_ident(request).await
|
||||||
|
|
||||||
};
|
};
|
||||||
Box::pin(fut)
|
Box::pin(fut)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let accept_compression_encodings = self.accept_compression_encodings;
|
||||||
|
let send_compression_encodings = self.send_compression_encodings;
|
||||||
let inner = self.inner.clone();
|
let inner = self.inner.clone();
|
||||||
let fut = async move {
|
let fut = async move {
|
||||||
let inner = inner.0;
|
let inner = inner.0;
|
||||||
let method = #service_ident(inner);
|
let method = #service_ident(inner);
|
||||||
let codec = #codec_name::default();
|
let codec = #codec_name::default();
|
||||||
|
|
||||||
let mut grpc = tonic::server::Grpc::new(codec);
|
let mut grpc = tonic::server::Grpc::new(codec)
|
||||||
|
.apply_compression_config(accept_compression_encodings, send_compression_encodings);
|
||||||
|
|
||||||
let res = grpc.server_streaming(method, req).await;
|
let res = grpc.server_streaming(method, req).await;
|
||||||
Ok(res)
|
Ok(res)
|
||||||
@@ -432,13 +475,16 @@ fn generate_client_streaming<T: Method>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let accept_compression_encodings = self.accept_compression_encodings;
|
||||||
|
let send_compression_encodings = self.send_compression_encodings;
|
||||||
let inner = self.inner.clone();
|
let inner = self.inner.clone();
|
||||||
let fut = async move {
|
let fut = async move {
|
||||||
let inner = inner.0;
|
let inner = inner.0;
|
||||||
let method = #service_ident(inner);
|
let method = #service_ident(inner);
|
||||||
let codec = #codec_name::default();
|
let codec = #codec_name::default();
|
||||||
|
|
||||||
let mut grpc = tonic::server::Grpc::new(codec);
|
let mut grpc = tonic::server::Grpc::new(codec)
|
||||||
|
.apply_compression_config(accept_compression_encodings, send_compression_encodings);
|
||||||
|
|
||||||
let res = grpc.client_streaming(method, req).await;
|
let res = grpc.client_streaming(method, req).await;
|
||||||
Ok(res)
|
Ok(res)
|
||||||
@@ -482,13 +528,16 @@ fn generate_streaming<T: Method>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let accept_compression_encodings = self.accept_compression_encodings;
|
||||||
|
let send_compression_encodings = self.send_compression_encodings;
|
||||||
let inner = self.inner.clone();
|
let inner = self.inner.clone();
|
||||||
let fut = async move {
|
let fut = async move {
|
||||||
let inner = inner.0;
|
let inner = inner.0;
|
||||||
let method = #service_ident(inner);
|
let method = #service_ident(inner);
|
||||||
let codec = #codec_name::default();
|
let codec = #codec_name::default();
|
||||||
|
|
||||||
let mut grpc = tonic::server::Grpc::new(codec);
|
let mut grpc = tonic::server::Grpc::new(codec)
|
||||||
|
.apply_compression_config(accept_compression_encodings, send_compression_encodings);
|
||||||
|
|
||||||
let res = grpc.streaming(method, req).await;
|
let res = grpc.streaming(method, req).await;
|
||||||
Ok(res)
|
Ok(res)
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ tls-roots-common = ["tls"]
|
|||||||
tls-roots = ["tls-roots-common", "rustls-native-certs"]
|
tls-roots = ["tls-roots-common", "rustls-native-certs"]
|
||||||
tls-webpki-roots = ["tls-roots-common", "webpki-roots"]
|
tls-webpki-roots = ["tls-roots-common", "webpki-roots"]
|
||||||
prost = ["prost1", "prost-derive"]
|
prost = ["prost1", "prost-derive"]
|
||||||
|
compression = ["flate2"]
|
||||||
|
|
||||||
# [[bench]]
|
# [[bench]]
|
||||||
# name = "bench_main"
|
# name = "bench_main"
|
||||||
@@ -82,6 +83,9 @@ tokio-rustls = { version = "0.22", optional = true }
|
|||||||
rustls-native-certs = { version = "0.5", optional = true }
|
rustls-native-certs = { version = "0.5", optional = true }
|
||||||
webpki-roots = { version = "0.21.1", optional = true }
|
webpki-roots = { version = "0.21.1", optional = true }
|
||||||
|
|
||||||
|
# compression
|
||||||
|
flate2 = { version = "1.0", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { version = "1.0", features = ["rt", "macros"] }
|
tokio = { version = "1.0", features = ["rt", "macros"] }
|
||||||
static_assertions = "1.0"
|
static_assertions = "1.0"
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ macro_rules! bench {
|
|||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
rt.block_on(async {
|
rt.block_on(async {
|
||||||
let decoder = MockDecoder::new($message_size);
|
let decoder = MockDecoder::new($message_size);
|
||||||
let mut stream = Streaming::new_request(decoder, body.clone());
|
let mut stream = Streaming::new_request(decoder, body.clone(), None);
|
||||||
|
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
while let Some(msg) = stream.message().await.unwrap() {
|
while let Some(msg) = stream.message().await.unwrap() {
|
||||||
|
|||||||
+152
-4
@@ -1,3 +1,5 @@
|
|||||||
|
#[cfg(feature = "compression")]
|
||||||
|
use crate::codec::compression::{CompressionEncoding, EnabledCompressionEncodings};
|
||||||
use crate::{
|
use crate::{
|
||||||
body::BoxBody,
|
body::BoxBody,
|
||||||
client::GrpcService,
|
client::GrpcService,
|
||||||
@@ -28,12 +30,102 @@ use std::fmt;
|
|||||||
/// [gRPC protocol definition]: https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests
|
/// [gRPC protocol definition]: https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests
|
||||||
pub struct Grpc<T> {
|
pub struct Grpc<T> {
|
||||||
inner: T,
|
inner: T,
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
/// Which compression encodings does the client accept?
|
||||||
|
accept_compression_encodings: EnabledCompressionEncodings,
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
/// The compression encoding that will be applied to requests.
|
||||||
|
send_compression_encodings: Option<CompressionEncoding>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Grpc<T> {
|
impl<T> Grpc<T> {
|
||||||
/// Creates a new gRPC client with the provided [`GrpcService`].
|
/// Creates a new gRPC client with the provided [`GrpcService`].
|
||||||
pub fn new(inner: T) -> Self {
|
pub fn new(inner: T) -> Self {
|
||||||
Self { inner }
|
Self {
|
||||||
|
inner,
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
send_compression_encodings: None,
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
accept_compression_encodings: EnabledCompressionEncodings::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compress requests with `gzip`.
|
||||||
|
///
|
||||||
|
/// Requires the server to accept `gzip` otherwise it might return an error.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// The most common way of using this is through a client generated by tonic-build:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use tonic::transport::Channel;
|
||||||
|
/// # struct TestClient<T>(T);
|
||||||
|
/// # impl<T> TestClient<T> {
|
||||||
|
/// # fn new(channel: T) -> Self { Self(channel) }
|
||||||
|
/// # fn send_gzip(self) -> Self { self }
|
||||||
|
/// # }
|
||||||
|
///
|
||||||
|
/// # async {
|
||||||
|
/// let channel = Channel::builder("127.0.0.1:3000".parse().unwrap())
|
||||||
|
/// .connect()
|
||||||
|
/// .await
|
||||||
|
/// .unwrap();
|
||||||
|
///
|
||||||
|
/// let client = TestClient::new(channel).send_gzip();
|
||||||
|
/// # };
|
||||||
|
/// ```
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "compression")))]
|
||||||
|
pub fn send_gzip(mut self) -> Self {
|
||||||
|
self.send_compression_encodings = Some(CompressionEncoding::Gzip);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[cfg(not(feature = "compression"))]
|
||||||
|
pub fn send_gzip(self) -> Self {
|
||||||
|
panic!(
|
||||||
|
"`send_gzip` called on a client but the `compression` feature is not enabled on tonic"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable accepting `gzip` compressed responses.
|
||||||
|
///
|
||||||
|
/// Requires the server to also support sending compressed responses.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// The most common way of using this is through a client generated by tonic-build:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use tonic::transport::Channel;
|
||||||
|
/// # struct TestClient<T>(T);
|
||||||
|
/// # impl<T> TestClient<T> {
|
||||||
|
/// # fn new(channel: T) -> Self { Self(channel) }
|
||||||
|
/// # fn accept_gzip(self) -> Self { self }
|
||||||
|
/// # }
|
||||||
|
///
|
||||||
|
/// # async {
|
||||||
|
/// let channel = Channel::builder("127.0.0.1:3000".parse().unwrap())
|
||||||
|
/// .connect()
|
||||||
|
/// .await
|
||||||
|
/// .unwrap();
|
||||||
|
///
|
||||||
|
/// let client = TestClient::new(channel).accept_gzip();
|
||||||
|
/// # };
|
||||||
|
/// ```
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "compression")))]
|
||||||
|
pub fn accept_gzip(mut self) -> Self {
|
||||||
|
self.accept_compression_encodings.enable_gzip();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[cfg(not(feature = "compression"))]
|
||||||
|
pub fn accept_gzip(self) -> Self {
|
||||||
|
panic!("`accept_gzip` called on a client but the `compression` feature is not enabled on tonic");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the inner [`GrpcService`] is able to accept a new request.
|
/// Check if the inner [`GrpcService`] is able to accept a new request.
|
||||||
@@ -145,7 +237,14 @@ impl<T> Grpc<T> {
|
|||||||
let uri = Uri::from_parts(parts).expect("path_and_query only is valid Uri");
|
let uri = Uri::from_parts(parts).expect("path_and_query only is valid Uri");
|
||||||
|
|
||||||
let request = request
|
let request = request
|
||||||
.map(|s| encode_client(codec.encoder(), s))
|
.map(|s| {
|
||||||
|
encode_client(
|
||||||
|
codec.encoder(),
|
||||||
|
s,
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
self.send_compression_encodings,
|
||||||
|
)
|
||||||
|
})
|
||||||
.map(BoxBody::new);
|
.map(BoxBody::new);
|
||||||
|
|
||||||
let mut request = request.into_http(uri);
|
let mut request = request.into_http(uri);
|
||||||
@@ -160,12 +259,38 @@ impl<T> Grpc<T> {
|
|||||||
.headers_mut()
|
.headers_mut()
|
||||||
.insert(CONTENT_TYPE, HeaderValue::from_static("application/grpc"));
|
.insert(CONTENT_TYPE, HeaderValue::from_static("application/grpc"));
|
||||||
|
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
{
|
||||||
|
if let Some(encoding) = self.send_compression_encodings {
|
||||||
|
request.headers_mut().insert(
|
||||||
|
crate::codec::compression::ENCODING_HEADER,
|
||||||
|
encoding.into_header_value(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(header_value) = self
|
||||||
|
.accept_compression_encodings
|
||||||
|
.into_accept_encoding_header_value()
|
||||||
|
{
|
||||||
|
request.headers_mut().insert(
|
||||||
|
crate::codec::compression::ACCEPT_ENCODING_HEADER,
|
||||||
|
header_value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let response = self
|
let response = self
|
||||||
.inner
|
.inner
|
||||||
.call(request)
|
.call(request)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| Status::from_error(err.into()))?;
|
.map_err(|err| Status::from_error(err.into()))?;
|
||||||
|
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
let encoding = CompressionEncoding::from_encoding_header(
|
||||||
|
response.headers(),
|
||||||
|
self.accept_compression_encodings,
|
||||||
|
)?;
|
||||||
|
|
||||||
let status_code = response.status();
|
let status_code = response.status();
|
||||||
let trailers_only_status = Status::from_header_map(response.headers());
|
let trailers_only_status = Status::from_header_map(response.headers());
|
||||||
|
|
||||||
@@ -183,7 +308,13 @@ impl<T> Grpc<T> {
|
|||||||
|
|
||||||
let response = response.map(|body| {
|
let response = response.map(|body| {
|
||||||
if expect_additional_trailers {
|
if expect_additional_trailers {
|
||||||
Streaming::new_response(codec.decoder(), body, status_code)
|
Streaming::new_response(
|
||||||
|
codec.decoder(),
|
||||||
|
body,
|
||||||
|
status_code,
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
encoding,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
Streaming::new_empty(codec.decoder(), body)
|
Streaming::new_empty(codec.decoder(), body)
|
||||||
}
|
}
|
||||||
@@ -197,12 +328,29 @@ impl<T: Clone> Clone for Grpc<T> {
|
|||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: self.inner.clone(),
|
inner: self.inner.clone(),
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
send_compression_encodings: self.send_compression_encodings,
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
accept_compression_encodings: self.accept_compression_encodings,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: fmt::Debug> fmt::Debug for Grpc<T> {
|
impl<T: fmt::Debug> fmt::Debug for Grpc<T> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.debug_struct("Grpc").field("inner", &self.inner).finish()
|
let mut f = f.debug_struct("Grpc");
|
||||||
|
|
||||||
|
f.field("inner", &self.inner);
|
||||||
|
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
f.field("compression_encoding", &self.send_compression_encodings);
|
||||||
|
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
f.field(
|
||||||
|
"accept_compression_encodings",
|
||||||
|
&self.accept_compression_encodings,
|
||||||
|
);
|
||||||
|
|
||||||
|
f.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,189 @@
|
|||||||
|
use super::encode::BUFFER_SIZE;
|
||||||
|
use crate::{metadata::MetadataValue, Status};
|
||||||
|
use bytes::{Buf, BufMut, BytesMut};
|
||||||
|
use flate2::read::{GzDecoder, GzEncoder};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
pub(crate) const ENCODING_HEADER: &str = "grpc-encoding";
|
||||||
|
pub(crate) const ACCEPT_ENCODING_HEADER: &str = "grpc-accept-encoding";
|
||||||
|
|
||||||
|
/// Struct used to configure which encodings are enabled on a server or channel.
|
||||||
|
#[derive(Debug, Default, Clone, Copy)]
|
||||||
|
pub struct EnabledCompressionEncodings {
|
||||||
|
pub(crate) gzip: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EnabledCompressionEncodings {
|
||||||
|
/// Check if `gzip` compression is enabled.
|
||||||
|
pub fn gzip(self) -> bool {
|
||||||
|
self.gzip
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable `gzip` compression.
|
||||||
|
pub fn enable_gzip(&mut self) {
|
||||||
|
self.gzip = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn into_accept_encoding_header_value(self) -> Option<http::HeaderValue> {
|
||||||
|
let Self { gzip } = self;
|
||||||
|
if gzip {
|
||||||
|
Some(http::HeaderValue::from_static("gzip,identity"))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The compression encodings Tonic supports.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum CompressionEncoding {
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
Gzip,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CompressionEncoding {
|
||||||
|
/// Based on the `grpc-accept-encoding` header, pick an encoding to use.
|
||||||
|
pub(crate) fn from_accept_encoding_header(
|
||||||
|
map: &http::HeaderMap,
|
||||||
|
enabled_encodings: EnabledCompressionEncodings,
|
||||||
|
) -> Option<Self> {
|
||||||
|
let header_value = map.get(ACCEPT_ENCODING_HEADER)?;
|
||||||
|
let header_value_str = header_value.to_str().ok()?;
|
||||||
|
|
||||||
|
let EnabledCompressionEncodings { gzip } = enabled_encodings;
|
||||||
|
|
||||||
|
split_by_comma(header_value_str).find_map(|value| match value {
|
||||||
|
"gzip" if gzip => Some(CompressionEncoding::Gzip),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the value of `grpc-encoding` header. Returns an error if the encoding isn't supported.
|
||||||
|
pub(crate) fn from_encoding_header(
|
||||||
|
map: &http::HeaderMap,
|
||||||
|
enabled_encodings: EnabledCompressionEncodings,
|
||||||
|
) -> Result<Option<Self>, Status> {
|
||||||
|
let header_value = if let Some(value) = map.get(ENCODING_HEADER) {
|
||||||
|
value
|
||||||
|
} else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
|
||||||
|
let header_value_str = if let Ok(value) = header_value.to_str() {
|
||||||
|
value
|
||||||
|
} else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
|
||||||
|
let EnabledCompressionEncodings { gzip } = enabled_encodings;
|
||||||
|
|
||||||
|
match header_value_str {
|
||||||
|
"gzip" if gzip => Ok(Some(CompressionEncoding::Gzip)),
|
||||||
|
other => {
|
||||||
|
let mut status = Status::unimplemented(format!(
|
||||||
|
"Content is compressed with `{}` which isn't supported",
|
||||||
|
other
|
||||||
|
));
|
||||||
|
|
||||||
|
let header_value = enabled_encodings
|
||||||
|
.into_accept_encoding_header_value()
|
||||||
|
.map(MetadataValue::unchecked_from_header_value)
|
||||||
|
.unwrap_or_else(|| MetadataValue::from_static("identity"));
|
||||||
|
status
|
||||||
|
.metadata_mut()
|
||||||
|
.insert(ACCEPT_ENCODING_HEADER, header_value);
|
||||||
|
|
||||||
|
Err(status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn into_header_value(self) -> http::HeaderValue {
|
||||||
|
match self {
|
||||||
|
CompressionEncoding::Gzip => http::HeaderValue::from_static("gzip"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for CompressionEncoding {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
CompressionEncoding::Gzip => write!(f, "gzip"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn split_by_comma(s: &str) -> impl Iterator<Item = &str> {
|
||||||
|
s.trim().split(',').map(|s| s.trim())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compress `len` bytes from `decompressed_buf` into `out_buf`.
|
||||||
|
pub(crate) fn compress(
|
||||||
|
encoding: CompressionEncoding,
|
||||||
|
decompressed_buf: &mut BytesMut,
|
||||||
|
out_buf: &mut BytesMut,
|
||||||
|
len: usize,
|
||||||
|
) -> Result<(), std::io::Error> {
|
||||||
|
let capacity = ((len / BUFFER_SIZE) + 1) * BUFFER_SIZE;
|
||||||
|
out_buf.reserve(capacity);
|
||||||
|
|
||||||
|
match encoding {
|
||||||
|
CompressionEncoding::Gzip => {
|
||||||
|
let mut gzip_encoder = GzEncoder::new(
|
||||||
|
&decompressed_buf[0..len],
|
||||||
|
// FIXME: support customizing the compression level
|
||||||
|
flate2::Compression::new(6),
|
||||||
|
);
|
||||||
|
let mut out_writer = out_buf.writer();
|
||||||
|
|
||||||
|
std::io::copy(&mut gzip_encoder, &mut out_writer)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
decompressed_buf.advance(len);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decompress `len` bytes from `compressed_buf` into `out_buf`.
|
||||||
|
pub(crate) fn decompress(
|
||||||
|
encoding: CompressionEncoding,
|
||||||
|
compressed_buf: &mut BytesMut,
|
||||||
|
out_buf: &mut BytesMut,
|
||||||
|
len: usize,
|
||||||
|
) -> Result<(), std::io::Error> {
|
||||||
|
let estimate_decompressed_len = len * 2;
|
||||||
|
let capacity = ((estimate_decompressed_len / BUFFER_SIZE) + 1) * BUFFER_SIZE;
|
||||||
|
out_buf.reserve(capacity);
|
||||||
|
|
||||||
|
match encoding {
|
||||||
|
CompressionEncoding::Gzip => {
|
||||||
|
let mut gzip_decoder = GzDecoder::new(&compressed_buf[0..len]);
|
||||||
|
let mut out_writer = out_buf.writer();
|
||||||
|
|
||||||
|
std::io::copy(&mut gzip_decoder, &mut out_writer)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compressed_buf.advance(len);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub(crate) enum SingleMessageCompressionOverride {
|
||||||
|
/// Inherit whatever compression is already configured. If the stream is compressed this
|
||||||
|
/// message will also be configured.
|
||||||
|
///
|
||||||
|
/// This is the default.
|
||||||
|
Inherit,
|
||||||
|
/// Don't compress this message, even if compression is enabled on the stream.
|
||||||
|
Disable,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for SingleMessageCompressionOverride {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Inherit
|
||||||
|
}
|
||||||
|
}
|
||||||
+97
-18
@@ -1,4 +1,6 @@
|
|||||||
use super::{DecodeBuf, Decoder};
|
#[cfg(feature = "compression")]
|
||||||
|
use super::compression::{decompress, CompressionEncoding};
|
||||||
|
use super::{DecodeBuf, Decoder, HEADER_SIZE};
|
||||||
use crate::{body::BoxBody, metadata::MetadataMap, Code, Status};
|
use crate::{body::BoxBody, metadata::MetadataMap, Code, Status};
|
||||||
use bytes::{Buf, BufMut, BytesMut};
|
use bytes::{Buf, BufMut, BytesMut};
|
||||||
use futures_core::Stream;
|
use futures_core::Stream;
|
||||||
@@ -25,6 +27,10 @@ pub struct Streaming<T> {
|
|||||||
direction: Direction,
|
direction: Direction,
|
||||||
buf: BytesMut,
|
buf: BytesMut,
|
||||||
trailers: Option<MetadataMap>,
|
trailers: Option<MetadataMap>,
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
decompress_buf: BytesMut,
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
encoding: Option<CompressionEncoding>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Unpin for Streaming<T> {}
|
impl<T> Unpin for Streaming<T> {}
|
||||||
@@ -43,13 +49,24 @@ enum Direction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Streaming<T> {
|
impl<T> Streaming<T> {
|
||||||
pub(crate) fn new_response<B, D>(decoder: D, body: B, status_code: StatusCode) -> Self
|
pub(crate) fn new_response<B, D>(
|
||||||
|
decoder: D,
|
||||||
|
body: B,
|
||||||
|
status_code: StatusCode,
|
||||||
|
#[cfg(feature = "compression")] encoding: Option<CompressionEncoding>,
|
||||||
|
) -> Self
|
||||||
where
|
where
|
||||||
B: Body + Send + Sync + 'static,
|
B: Body + Send + Sync + 'static,
|
||||||
B::Error: Into<crate::Error>,
|
B::Error: Into<crate::Error>,
|
||||||
D: Decoder<Item = T, Error = Status> + Send + Sync + 'static,
|
D: Decoder<Item = T, Error = Status> + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
Self::new(decoder, body, Direction::Response(status_code))
|
Self::new(
|
||||||
|
decoder,
|
||||||
|
body,
|
||||||
|
Direction::Response(status_code),
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
encoding,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn new_empty<B, D>(decoder: D, body: B) -> Self
|
pub(crate) fn new_empty<B, D>(decoder: D, body: B) -> Self
|
||||||
@@ -58,20 +75,41 @@ impl<T> Streaming<T> {
|
|||||||
B::Error: Into<crate::Error>,
|
B::Error: Into<crate::Error>,
|
||||||
D: Decoder<Item = T, Error = Status> + Send + Sync + 'static,
|
D: Decoder<Item = T, Error = Status> + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
Self::new(decoder, body, Direction::EmptyResponse)
|
Self::new(
|
||||||
|
decoder,
|
||||||
|
body,
|
||||||
|
Direction::EmptyResponse,
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
None,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn new_request<B, D>(decoder: D, body: B) -> Self
|
pub fn new_request<B, D>(
|
||||||
|
decoder: D,
|
||||||
|
body: B,
|
||||||
|
#[cfg(feature = "compression")] encoding: Option<CompressionEncoding>,
|
||||||
|
) -> Self
|
||||||
where
|
where
|
||||||
B: Body + Send + Sync + 'static,
|
B: Body + Send + Sync + 'static,
|
||||||
B::Error: Into<crate::Error>,
|
B::Error: Into<crate::Error>,
|
||||||
D: Decoder<Item = T, Error = Status> + Send + Sync + 'static,
|
D: Decoder<Item = T, Error = Status> + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
Self::new(decoder, body, Direction::Request)
|
Self::new(
|
||||||
|
decoder,
|
||||||
|
body,
|
||||||
|
Direction::Request,
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
encoding,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new<B, D>(decoder: D, body: B, direction: Direction) -> Self
|
fn new<B, D>(
|
||||||
|
decoder: D,
|
||||||
|
body: B,
|
||||||
|
direction: Direction,
|
||||||
|
#[cfg(feature = "compression")] encoding: Option<CompressionEncoding>,
|
||||||
|
) -> Self
|
||||||
where
|
where
|
||||||
B: Body + Send + Sync + 'static,
|
B: Body + Send + Sync + 'static,
|
||||||
B::Error: Into<crate::Error>,
|
B::Error: Into<crate::Error>,
|
||||||
@@ -87,6 +125,10 @@ impl<T> Streaming<T> {
|
|||||||
direction,
|
direction,
|
||||||
buf: BytesMut::with_capacity(BUFFER_SIZE),
|
buf: BytesMut::with_capacity(BUFFER_SIZE),
|
||||||
trailers: None,
|
trailers: None,
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
decompress_buf: BytesMut::new(),
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
encoding,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,18 +198,21 @@ impl<T> Streaming<T> {
|
|||||||
|
|
||||||
fn decode_chunk(&mut self) -> Result<Option<T>, Status> {
|
fn decode_chunk(&mut self) -> Result<Option<T>, Status> {
|
||||||
if let State::ReadHeader = self.state {
|
if let State::ReadHeader = self.state {
|
||||||
if self.buf.remaining() < 5 {
|
if self.buf.remaining() < HEADER_SIZE {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let is_compressed = match self.buf.get_u8() {
|
let is_compressed = match self.buf.get_u8() {
|
||||||
0 => false,
|
0 => false,
|
||||||
1 => {
|
1 => {
|
||||||
trace!("message compressed, compression not supported yet");
|
if cfg!(feature = "compression") {
|
||||||
return Err(Status::new(
|
true
|
||||||
Code::Unimplemented,
|
} else {
|
||||||
"Message compressed, compression not supported yet.".to_string(),
|
return Err(Status::new(
|
||||||
));
|
Code::Unimplemented,
|
||||||
|
"Message compressed, compression support not enabled.".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
f => {
|
f => {
|
||||||
trace!("unexpected compression flag");
|
trace!("unexpected compression flag");
|
||||||
@@ -191,17 +236,51 @@ impl<T> Streaming<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let State::ReadBody { len, .. } = &self.state {
|
if let State::ReadBody { len, compression } = &self.state {
|
||||||
// if we haven't read enough of the message then return and keep
|
// if we haven't read enough of the message then return and keep
|
||||||
// reading
|
// reading
|
||||||
if self.buf.remaining() < *len || self.buf.len() < *len {
|
if self.buf.remaining() < *len || self.buf.len() < *len {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
return match self
|
let decoding_result = if *compression {
|
||||||
.decoder
|
#[cfg(feature = "compression")]
|
||||||
.decode(&mut DecodeBuf::new(&mut self.buf, *len))
|
{
|
||||||
{
|
self.decompress_buf.clear();
|
||||||
|
|
||||||
|
if let Err(err) = decompress(
|
||||||
|
self.encoding.unwrap_or_else(|| {
|
||||||
|
unreachable!("message was compressed but `Streaming.encoding` was `None`. This is a bug in Tonic. Please file an issue")
|
||||||
|
}),
|
||||||
|
&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: {}",
|
||||||
|
err, status
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
format!("Error decompressing: {}, while sending request", err)
|
||||||
|
};
|
||||||
|
return Err(Status::new(Code::Internal, message));
|
||||||
|
}
|
||||||
|
let decompressed_len = self.decompress_buf.len();
|
||||||
|
self.decoder.decode(&mut DecodeBuf::new(
|
||||||
|
&mut self.decompress_buf,
|
||||||
|
decompressed_len,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "compression"))]
|
||||||
|
unreachable!("should not take this branch if compression is disabled")
|
||||||
|
} else {
|
||||||
|
self.decoder
|
||||||
|
.decode(&mut DecodeBuf::new(&mut self.buf, *len))
|
||||||
|
};
|
||||||
|
|
||||||
|
return match decoding_result {
|
||||||
Ok(Some(msg)) => {
|
Ok(Some(msg)) => {
|
||||||
self.state = State::ReadHeader;
|
self.state = State::ReadHeader;
|
||||||
Ok(Some(msg))
|
Ok(Some(msg))
|
||||||
|
|||||||
+76
-12
@@ -1,4 +1,6 @@
|
|||||||
use super::{EncodeBuf, Encoder};
|
#[cfg(feature = "compression")]
|
||||||
|
use super::compression::{compress, CompressionEncoding, SingleMessageCompressionOverride};
|
||||||
|
use super::{EncodeBuf, Encoder, HEADER_SIZE};
|
||||||
use crate::{Code, Status};
|
use crate::{Code, Status};
|
||||||
use bytes::{BufMut, Bytes, BytesMut};
|
use bytes::{BufMut, Bytes, BytesMut};
|
||||||
use futures_core::{Stream, TryStream};
|
use futures_core::{Stream, TryStream};
|
||||||
@@ -11,62 +13,124 @@ use std::{
|
|||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
};
|
};
|
||||||
|
|
||||||
const BUFFER_SIZE: usize = 8 * 1024;
|
pub(super) const BUFFER_SIZE: usize = 8 * 1024;
|
||||||
|
|
||||||
pub(crate) fn encode_server<T, U>(
|
pub(crate) fn encode_server<T, U>(
|
||||||
encoder: T,
|
encoder: T,
|
||||||
source: U,
|
source: U,
|
||||||
|
#[cfg(feature = "compression")] compression_encoding: Option<CompressionEncoding>,
|
||||||
|
#[cfg(feature = "compression")] compression_override: SingleMessageCompressionOverride,
|
||||||
) -> EncodeBody<impl Stream<Item = Result<Bytes, Status>>>
|
) -> EncodeBody<impl Stream<Item = Result<Bytes, Status>>>
|
||||||
where
|
where
|
||||||
T: Encoder<Error = Status> + Send + Sync + 'static,
|
T: Encoder<Error = Status> + Send + Sync + 'static,
|
||||||
T::Item: Send + Sync,
|
T::Item: Send + Sync,
|
||||||
U: Stream<Item = Result<T::Item, Status>> + Send + Sync + 'static,
|
U: Stream<Item = Result<T::Item, Status>> + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
let stream = encode(encoder, source).into_stream();
|
let stream = encode(
|
||||||
|
encoder,
|
||||||
|
source,
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
compression_encoding,
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
compression_override,
|
||||||
|
)
|
||||||
|
.into_stream();
|
||||||
|
|
||||||
EncodeBody::new_server(stream)
|
EncodeBody::new_server(stream)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn encode_client<T, U>(
|
pub(crate) fn encode_client<T, U>(
|
||||||
encoder: T,
|
encoder: T,
|
||||||
source: U,
|
source: U,
|
||||||
|
#[cfg(feature = "compression")] compression_encoding: Option<CompressionEncoding>,
|
||||||
) -> EncodeBody<impl Stream<Item = Result<Bytes, Status>>>
|
) -> EncodeBody<impl Stream<Item = Result<Bytes, Status>>>
|
||||||
where
|
where
|
||||||
T: Encoder<Error = Status> + Send + Sync + 'static,
|
T: Encoder<Error = Status> + Send + Sync + 'static,
|
||||||
T::Item: Send + Sync,
|
T::Item: Send + Sync,
|
||||||
U: Stream<Item = T::Item> + Send + Sync + 'static,
|
U: Stream<Item = T::Item> + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
let stream = encode(encoder, source.map(Ok)).into_stream();
|
let stream = encode(
|
||||||
|
encoder,
|
||||||
|
source.map(Ok),
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
compression_encoding,
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
SingleMessageCompressionOverride::default(),
|
||||||
|
)
|
||||||
|
.into_stream();
|
||||||
EncodeBody::new_client(stream)
|
EncodeBody::new_client(stream)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode<T, U>(mut encoder: T, source: U) -> impl TryStream<Ok = Bytes, Error = Status>
|
fn encode<T, U>(
|
||||||
|
mut encoder: T,
|
||||||
|
source: U,
|
||||||
|
#[cfg(feature = "compression")] compression_encoding: Option<CompressionEncoding>,
|
||||||
|
#[cfg(feature = "compression")] compression_override: SingleMessageCompressionOverride,
|
||||||
|
) -> impl TryStream<Ok = Bytes, Error = Status>
|
||||||
where
|
where
|
||||||
T: Encoder<Error = Status>,
|
T: Encoder<Error = Status>,
|
||||||
U: Stream<Item = Result<T::Item, Status>>,
|
U: Stream<Item = Result<T::Item, Status>>,
|
||||||
{
|
{
|
||||||
async_stream::stream! {
|
async_stream::stream! {
|
||||||
let mut buf = BytesMut::with_capacity(BUFFER_SIZE);
|
let mut buf = BytesMut::with_capacity(BUFFER_SIZE);
|
||||||
|
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
let (compression_enabled_for_stream, mut uncompression_buf) = match compression_encoding {
|
||||||
|
Some(CompressionEncoding::Gzip) => (true, BytesMut::with_capacity(BUFFER_SIZE)),
|
||||||
|
None => (false, BytesMut::new()),
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
let compress_item = compression_enabled_for_stream && compression_override == SingleMessageCompressionOverride::Inherit;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "compression"))]
|
||||||
|
let compress_item = false;
|
||||||
|
|
||||||
futures_util::pin_mut!(source);
|
futures_util::pin_mut!(source);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match source.next().await {
|
match source.next().await {
|
||||||
Some(Ok(item)) => {
|
Some(Ok(item)) => {
|
||||||
buf.reserve(5);
|
buf.reserve(HEADER_SIZE);
|
||||||
unsafe {
|
unsafe {
|
||||||
buf.advance_mut(5);
|
buf.advance_mut(HEADER_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if compress_item {
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
{
|
||||||
|
uncompression_buf.clear();
|
||||||
|
|
||||||
|
encoder.encode(item, &mut EncodeBuf::new(&mut uncompression_buf))
|
||||||
|
.map_err(|err| Status::internal(format!("Error encoding: {}", err)))?;
|
||||||
|
|
||||||
|
let uncompressed_len = uncompression_buf.len();
|
||||||
|
|
||||||
|
compress(
|
||||||
|
compression_encoding.unwrap(),
|
||||||
|
&mut uncompression_buf,
|
||||||
|
&mut buf,
|
||||||
|
uncompressed_len,
|
||||||
|
).map_err(|err| Status::internal(format!("Error compressing: {}", err)))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "compression"))]
|
||||||
|
unreachable!("compression disabled, should not take this branch");
|
||||||
|
} else {
|
||||||
|
encoder.encode(item, &mut EncodeBuf::new(&mut buf))
|
||||||
|
.map_err(|err| Status::internal(format!("Error encoding: {}", err)))?;
|
||||||
}
|
}
|
||||||
encoder.encode(item, &mut EncodeBuf::new(&mut buf)).map_err(drop).unwrap();
|
|
||||||
|
|
||||||
// now that we know length, we can write the header
|
// now that we know length, we can write the header
|
||||||
let len = buf.len() - 5;
|
let len = buf.len() - HEADER_SIZE;
|
||||||
assert!(len <= std::u32::MAX as usize);
|
assert!(len <= std::u32::MAX as usize);
|
||||||
{
|
{
|
||||||
let mut buf = &mut buf[..5];
|
let mut buf = &mut buf[..HEADER_SIZE];
|
||||||
buf.put_u8(0); // byte must be 0, reserve doesn't auto-zero
|
buf.put_u8(compress_item as u8);
|
||||||
buf.put_u32(len as u32);
|
buf.put_u32(len as u32);
|
||||||
}
|
}
|
||||||
|
|
||||||
yield Ok(buf.split_to(len + 5).freeze());
|
yield Ok(buf.split_to(len + HEADER_SIZE).freeze());
|
||||||
},
|
},
|
||||||
Some(Err(status)) => yield Err(status),
|
Some(Err(status)) => yield Err(status),
|
||||||
None => break,
|
None => break,
|
||||||
|
|||||||
+16
-3
@@ -4,20 +4,33 @@
|
|||||||
//! and a protobuf codec based on prost.
|
//! and a protobuf codec based on prost.
|
||||||
|
|
||||||
mod buffer;
|
mod buffer;
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
pub(crate) mod compression;
|
||||||
mod decode;
|
mod decode;
|
||||||
mod encode;
|
mod encode;
|
||||||
#[cfg(feature = "prost")]
|
#[cfg(feature = "prost")]
|
||||||
mod prost;
|
mod prost;
|
||||||
|
|
||||||
|
use crate::Status;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
pub use self::decode::Streaming;
|
|
||||||
pub(crate) use self::encode::{encode_client, encode_server};
|
pub(crate) use self::encode::{encode_client, encode_server};
|
||||||
|
|
||||||
|
pub use self::buffer::{DecodeBuf, EncodeBuf};
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "compression")))]
|
||||||
|
pub use self::compression::{CompressionEncoding, EnabledCompressionEncodings};
|
||||||
|
pub use self::decode::Streaming;
|
||||||
#[cfg(feature = "prost")]
|
#[cfg(feature = "prost")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "prost")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "prost")))]
|
||||||
pub use self::prost::ProstCodec;
|
pub use self::prost::ProstCodec;
|
||||||
use crate::Status;
|
|
||||||
pub use buffer::{DecodeBuf, EncodeBuf};
|
// 5 bytes
|
||||||
|
const HEADER_SIZE: usize =
|
||||||
|
// compression flag
|
||||||
|
std::mem::size_of::<u8>() +
|
||||||
|
// data length
|
||||||
|
std::mem::size_of::<u32>();
|
||||||
|
|
||||||
/// Trait that knows how to encode and decode gRPC messages.
|
/// Trait that knows how to encode and decode gRPC messages.
|
||||||
pub trait Codec: Default {
|
pub trait Codec: Default {
|
||||||
|
|||||||
@@ -77,7 +77,10 @@ fn from_decode_error(error: prost1::DecodeError) -> crate::Status {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::codec::{encode_server, DecodeBuf, Decoder, EncodeBuf, Encoder, Streaming};
|
use crate::codec::compression::SingleMessageCompressionOverride;
|
||||||
|
use crate::codec::{
|
||||||
|
encode_server, DecodeBuf, Decoder, EncodeBuf, Encoder, Streaming, HEADER_SIZE,
|
||||||
|
};
|
||||||
use crate::Status;
|
use crate::Status;
|
||||||
use bytes::{Buf, BufMut, BytesMut};
|
use bytes::{Buf, BufMut, BytesMut};
|
||||||
use http_body::Body;
|
use http_body::Body;
|
||||||
@@ -92,7 +95,7 @@ mod tests {
|
|||||||
|
|
||||||
let mut buf = BytesMut::new();
|
let mut buf = BytesMut::new();
|
||||||
|
|
||||||
buf.reserve(msg.len() + 5);
|
buf.reserve(msg.len() + HEADER_SIZE);
|
||||||
buf.put_u8(0);
|
buf.put_u8(0);
|
||||||
buf.put_u32(msg.len() as u32);
|
buf.put_u32(msg.len() as u32);
|
||||||
|
|
||||||
@@ -100,7 +103,7 @@ mod tests {
|
|||||||
|
|
||||||
let body = body::MockBody::new(&buf[..], 10005, 0);
|
let body = body::MockBody::new(&buf[..], 10005, 0);
|
||||||
|
|
||||||
let mut stream = Streaming::new_request(decoder, body);
|
let mut stream = Streaming::new_request(decoder, body, None);
|
||||||
|
|
||||||
let mut i = 0usize;
|
let mut i = 0usize;
|
||||||
while let Some(output_msg) = stream.message().await.unwrap() {
|
while let Some(output_msg) = stream.message().await.unwrap() {
|
||||||
@@ -119,7 +122,12 @@ mod tests {
|
|||||||
let messages = std::iter::repeat_with(move || Ok::<_, Status>(msg.clone())).take(10000);
|
let messages = std::iter::repeat_with(move || Ok::<_, Status>(msg.clone())).take(10000);
|
||||||
let source = futures_util::stream::iter(messages);
|
let source = futures_util::stream::iter(messages);
|
||||||
|
|
||||||
let body = encode_server(encoder, source);
|
let body = encode_server(
|
||||||
|
encoder,
|
||||||
|
source,
|
||||||
|
None,
|
||||||
|
SingleMessageCompressionOverride::default(),
|
||||||
|
);
|
||||||
|
|
||||||
futures_util::pin_mut!(body);
|
futures_util::pin_mut!(body);
|
||||||
|
|
||||||
@@ -216,6 +224,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::drop_ref)]
|
||||||
fn poll_trailers(
|
fn poll_trailers(
|
||||||
self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
cx: &mut Context<'_>,
|
cx: &mut Context<'_>,
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ pub use std::sync::Arc;
|
|||||||
pub use std::task::{Context, Poll};
|
pub use std::task::{Context, Poll};
|
||||||
pub use tower_service::Service;
|
pub use tower_service::Service;
|
||||||
pub type StdError = Box<dyn std::error::Error + Send + Sync + 'static>;
|
pub type StdError = Box<dyn std::error::Error + Send + Sync + 'static>;
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
pub use crate::codec::{CompressionEncoding, EnabledCompressionEncodings};
|
||||||
pub use crate::service::interceptor::InterceptedService;
|
pub use crate::service::interceptor::InterceptedService;
|
||||||
pub use http_body::Body;
|
pub use http_body::Body;
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,9 @@
|
|||||||
//! - `tls-webpki-roots`: Add the standard trust roots from the `webpki-roots` crate to
|
//! - `tls-webpki-roots`: Add the standard trust roots from the `webpki-roots` crate to
|
||||||
//! `rustls`-based gRPC clients. Not enabled by default.
|
//! `rustls`-based gRPC clients. Not enabled by default.
|
||||||
//! - `prost`: Enables the [`prost`] based gRPC [`Codec`] implementation.
|
//! - `prost`: Enables the [`prost`] based gRPC [`Codec`] implementation.
|
||||||
|
//! - `compression`: Enables compressing requests, responses, and streams. Note
|
||||||
|
//! that you must enable the `compression` feature on both `tonic` and
|
||||||
|
//! `tonic-build` to use it. Depends on [flate2]. Not enabled by default.
|
||||||
//!
|
//!
|
||||||
//! # Structure
|
//! # Structure
|
||||||
//!
|
//!
|
||||||
@@ -62,6 +65,7 @@
|
|||||||
//! [`rustls`]: https://docs.rs/rustls
|
//! [`rustls`]: https://docs.rs/rustls
|
||||||
//! [`client`]: client/index.html
|
//! [`client`]: client/index.html
|
||||||
//! [`transport`]: transport/index.html
|
//! [`transport`]: transport/index.html
|
||||||
|
//! [flate2]: https://crates.io/crates/flate2
|
||||||
|
|
||||||
#![recursion_limit = "256"]
|
#![recursion_limit = "256"]
|
||||||
#![allow(clippy::inconsistent_struct_constructor)]
|
#![allow(clippy::inconsistent_struct_constructor)]
|
||||||
|
|||||||
@@ -200,12 +200,11 @@ pub(crate) const GRPC_TIMEOUT_HEADER: &str = "grpc-timeout";
|
|||||||
|
|
||||||
impl MetadataMap {
|
impl MetadataMap {
|
||||||
// Headers reserved by the gRPC protocol.
|
// Headers reserved by the gRPC protocol.
|
||||||
pub(crate) const GRPC_RESERVED_HEADERS: [&'static str; 7] = [
|
pub(crate) const GRPC_RESERVED_HEADERS: [&'static str; 6] = [
|
||||||
"te",
|
"te",
|
||||||
"user-agent",
|
"user-agent",
|
||||||
"content-type",
|
"content-type",
|
||||||
"grpc-message",
|
"grpc-message",
|
||||||
"grpc-encoding",
|
|
||||||
"grpc-message-type",
|
"grpc-message-type",
|
||||||
"grpc-status",
|
"grpc-status",
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -107,6 +107,21 @@ impl<T> Response<T> {
|
|||||||
pub fn extensions_mut(&mut self) -> &mut Extensions {
|
pub fn extensions_mut(&mut self) -> &mut Extensions {
|
||||||
&mut self.extensions
|
&mut self.extensions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Disable compression of the response body.
|
||||||
|
///
|
||||||
|
/// This disables compression of the body of this response, even if compression is enabled on
|
||||||
|
/// the server.
|
||||||
|
///
|
||||||
|
/// **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 = "compression")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "compression")))]
|
||||||
|
pub fn disable_compression(&mut self) {
|
||||||
|
self.extensions_mut()
|
||||||
|
.insert(crate::codec::compression::SingleMessageCompressionOverride::Disable);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
+308
-26
@@ -1,3 +1,7 @@
|
|||||||
|
#[cfg(feature = "compression")]
|
||||||
|
use crate::codec::compression::{
|
||||||
|
CompressionEncoding, EnabledCompressionEncodings, SingleMessageCompressionOverride,
|
||||||
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
body::BoxBody,
|
body::BoxBody,
|
||||||
codec::{encode_server, Codec, Streaming},
|
codec::{encode_server, Codec, Streaming},
|
||||||
@@ -9,6 +13,15 @@ use futures_util::{future, stream, TryStreamExt};
|
|||||||
use http_body::Body;
|
use http_body::Body;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
macro_rules! t {
|
||||||
|
($result:expr) => {
|
||||||
|
match $result {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(status) => return status.to_http(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// A gRPC Server handler.
|
/// A gRPC Server handler.
|
||||||
///
|
///
|
||||||
/// This will wrap some inner [`Codec`] and provide utilities to handle
|
/// This will wrap some inner [`Codec`] and provide utilities to handle
|
||||||
@@ -20,6 +33,12 @@ use std::fmt;
|
|||||||
/// implements some [`Body`].
|
/// implements some [`Body`].
|
||||||
pub struct Grpc<T> {
|
pub struct Grpc<T> {
|
||||||
codec: T,
|
codec: T,
|
||||||
|
/// Which compression encodings does the server accept for requests?
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
accept_compression_encodings: EnabledCompressionEncodings,
|
||||||
|
/// Which compression encodings might the server use for responses.
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
send_compression_encodings: EnabledCompressionEncodings,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Grpc<T>
|
impl<T> Grpc<T>
|
||||||
@@ -29,7 +48,121 @@ where
|
|||||||
{
|
{
|
||||||
/// Creates a new gRPC server with the provided [`Codec`].
|
/// Creates a new gRPC server with the provided [`Codec`].
|
||||||
pub fn new(codec: T) -> Self {
|
pub fn new(codec: T) -> Self {
|
||||||
Self { codec }
|
Self {
|
||||||
|
codec,
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
accept_compression_encodings: EnabledCompressionEncodings::default(),
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
send_compression_encodings: EnabledCompressionEncodings::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable accepting `gzip` compressed requests.
|
||||||
|
///
|
||||||
|
/// If a request with an unsupported encoding is received the server will respond with
|
||||||
|
/// [`Code::UnUnimplemented`](crate::Code).
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// The most common way of using this is through a server generated by tonic-build:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # struct Svc;
|
||||||
|
/// # struct ExampleServer<T>(T);
|
||||||
|
/// # impl<T> ExampleServer<T> {
|
||||||
|
/// # fn new(svc: T) -> Self { Self(svc) }
|
||||||
|
/// # fn accept_gzip(self) -> Self { self }
|
||||||
|
/// # }
|
||||||
|
/// # #[tonic::async_trait]
|
||||||
|
/// # trait Example {}
|
||||||
|
///
|
||||||
|
/// #[tonic::async_trait]
|
||||||
|
/// impl Example for Svc {
|
||||||
|
/// // ...
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// let service = ExampleServer::new(Svc).accept_gzip();
|
||||||
|
/// ```
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "compression")))]
|
||||||
|
pub fn accept_gzip(mut self) -> Self {
|
||||||
|
self.accept_compression_encodings.enable_gzip();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[cfg(not(feature = "compression"))]
|
||||||
|
pub fn accept_gzip(self) -> Self {
|
||||||
|
panic!("`accept_gzip` called on a server but the `compression` feature is not enabled on tonic");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable sending `gzip` compressed responses.
|
||||||
|
///
|
||||||
|
/// Requires the client to also support receiving compressed responses.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// The most common way of using this is through a server generated by tonic-build:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # struct Svc;
|
||||||
|
/// # struct ExampleServer<T>(T);
|
||||||
|
/// # impl<T> ExampleServer<T> {
|
||||||
|
/// # fn new(svc: T) -> Self { Self(svc) }
|
||||||
|
/// # fn send_gzip(self) -> Self { self }
|
||||||
|
/// # }
|
||||||
|
/// # #[tonic::async_trait]
|
||||||
|
/// # trait Example {}
|
||||||
|
///
|
||||||
|
/// #[tonic::async_trait]
|
||||||
|
/// impl Example for Svc {
|
||||||
|
/// // ...
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// let service = ExampleServer::new(Svc).send_gzip();
|
||||||
|
/// ```
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "compression")))]
|
||||||
|
pub fn send_gzip(mut self) -> Self {
|
||||||
|
self.send_compression_encodings.enable_gzip();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[cfg(not(feature = "compression"))]
|
||||||
|
pub fn send_gzip(self) -> Self {
|
||||||
|
panic!(
|
||||||
|
"`send_gzip` called on a server but the `compression` feature is not enabled on tonic"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn apply_compression_config(
|
||||||
|
self,
|
||||||
|
accept_encodings: EnabledCompressionEncodings,
|
||||||
|
send_encodings: EnabledCompressionEncodings,
|
||||||
|
) -> Self {
|
||||||
|
let mut this = self;
|
||||||
|
|
||||||
|
let EnabledCompressionEncodings { gzip: accept_gzip } = accept_encodings;
|
||||||
|
if accept_gzip {
|
||||||
|
this = this.accept_gzip();
|
||||||
|
}
|
||||||
|
|
||||||
|
let EnabledCompressionEncodings { gzip: send_gzip } = send_encodings;
|
||||||
|
if send_gzip {
|
||||||
|
this = this.send_gzip();
|
||||||
|
}
|
||||||
|
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "compression"))]
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
pub fn apply_compression_config(self, accept_encodings: (), send_encodings: ()) -> Self {
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle a single unary gRPC request.
|
/// Handle a single unary gRPC request.
|
||||||
@@ -43,13 +176,23 @@ where
|
|||||||
B: Body + Send + Sync + 'static,
|
B: Body + Send + Sync + 'static,
|
||||||
B::Error: Into<crate::Error> + Send,
|
B::Error: Into<crate::Error> + Send,
|
||||||
{
|
{
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
let accept_encoding = CompressionEncoding::from_accept_encoding_header(
|
||||||
|
req.headers(),
|
||||||
|
self.send_compression_encodings,
|
||||||
|
);
|
||||||
|
|
||||||
let request = match self.map_request_unary(req).await {
|
let request = match self.map_request_unary(req).await {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(status) => {
|
Err(status) => {
|
||||||
return self
|
return self
|
||||||
.map_response::<stream::Once<future::Ready<Result<T::Encode, Status>>>>(Err(
|
.map_response::<stream::Once<future::Ready<Result<T::Encode, Status>>>>(
|
||||||
status,
|
Err(status),
|
||||||
));
|
#[cfg(feature = "compression")]
|
||||||
|
accept_encoding,
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
SingleMessageCompressionOverride::default(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -58,7 +201,16 @@ where
|
|||||||
.await
|
.await
|
||||||
.map(|r| r.map(|m| stream::once(future::ok(m))));
|
.map(|r| r.map(|m| stream::once(future::ok(m))));
|
||||||
|
|
||||||
self.map_response(response)
|
#[cfg(feature = "compression")]
|
||||||
|
let compression_override = compression_override_from_response(&response);
|
||||||
|
|
||||||
|
self.map_response(
|
||||||
|
response,
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
accept_encoding,
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
compression_override,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle a server side streaming request.
|
/// Handle a server side streaming request.
|
||||||
@@ -73,16 +225,36 @@ where
|
|||||||
B: Body + Send + Sync + 'static,
|
B: Body + Send + Sync + 'static,
|
||||||
B::Error: Into<crate::Error> + Send,
|
B::Error: Into<crate::Error> + Send,
|
||||||
{
|
{
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
let accept_encoding = CompressionEncoding::from_accept_encoding_header(
|
||||||
|
req.headers(),
|
||||||
|
self.send_compression_encodings,
|
||||||
|
);
|
||||||
|
|
||||||
let request = match self.map_request_unary(req).await {
|
let request = match self.map_request_unary(req).await {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(status) => {
|
Err(status) => {
|
||||||
return self.map_response::<S::ResponseStream>(Err(status));
|
return self.map_response::<S::ResponseStream>(
|
||||||
|
Err(status),
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
accept_encoding,
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
SingleMessageCompressionOverride::default(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = service.call(request).await;
|
let response = service.call(request).await;
|
||||||
|
|
||||||
self.map_response(response)
|
self.map_response(
|
||||||
|
response,
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
accept_encoding,
|
||||||
|
// disabling compression of individual stream items must be done on
|
||||||
|
// the items themselves
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
SingleMessageCompressionOverride::default(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle a client side streaming gRPC request.
|
/// Handle a client side streaming gRPC request.
|
||||||
@@ -96,12 +268,29 @@ where
|
|||||||
B: Body + Send + Sync + 'static,
|
B: Body + Send + Sync + 'static,
|
||||||
B::Error: Into<crate::Error> + Send + 'static,
|
B::Error: Into<crate::Error> + Send + 'static,
|
||||||
{
|
{
|
||||||
let request = self.map_request_streaming(req);
|
#[cfg(feature = "compression")]
|
||||||
|
let accept_encoding = CompressionEncoding::from_accept_encoding_header(
|
||||||
|
req.headers(),
|
||||||
|
self.send_compression_encodings,
|
||||||
|
);
|
||||||
|
|
||||||
|
let request = t!(self.map_request_streaming(req));
|
||||||
|
|
||||||
let response = service
|
let response = service
|
||||||
.call(request)
|
.call(request)
|
||||||
.await
|
.await
|
||||||
.map(|r| r.map(|m| stream::once(future::ok(m))));
|
.map(|r| r.map(|m| stream::once(future::ok(m))));
|
||||||
self.map_response(response)
|
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
let compression_override = compression_override_from_response(&response);
|
||||||
|
|
||||||
|
self.map_response(
|
||||||
|
response,
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
accept_encoding,
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
compression_override,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle a bi-directional streaming gRPC request.
|
/// Handle a bi-directional streaming gRPC request.
|
||||||
@@ -116,9 +305,23 @@ where
|
|||||||
B: Body + Send + Sync + 'static,
|
B: Body + Send + Sync + 'static,
|
||||||
B::Error: Into<crate::Error> + Send,
|
B::Error: Into<crate::Error> + Send,
|
||||||
{
|
{
|
||||||
let request = self.map_request_streaming(req);
|
#[cfg(feature = "compression")]
|
||||||
|
let accept_encoding = CompressionEncoding::from_accept_encoding_header(
|
||||||
|
req.headers(),
|
||||||
|
self.send_compression_encodings,
|
||||||
|
);
|
||||||
|
|
||||||
|
let request = t!(self.map_request_streaming(req));
|
||||||
|
|
||||||
let response = service.call(request).await;
|
let response = service.call(request).await;
|
||||||
self.map_response(response)
|
|
||||||
|
self.map_response(
|
||||||
|
response,
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
accept_encoding,
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
SingleMessageCompressionOverride::default(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn map_request_unary<B>(
|
async fn map_request_unary<B>(
|
||||||
@@ -129,7 +332,16 @@ where
|
|||||||
B: Body + Send + Sync + 'static,
|
B: Body + Send + Sync + 'static,
|
||||||
B::Error: Into<crate::Error> + Send,
|
B::Error: Into<crate::Error> + Send,
|
||||||
{
|
{
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
let request_compression_encoding = self.request_encoding_if_supported(&request)?;
|
||||||
|
|
||||||
let (parts, body) = request.into_parts();
|
let (parts, body) = request.into_parts();
|
||||||
|
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
let stream =
|
||||||
|
Streaming::new_request(self.codec.decoder(), body, request_compression_encoding);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "compression"))]
|
||||||
let stream = Streaming::new_request(self.codec.decoder(), body);
|
let stream = Streaming::new_request(self.codec.decoder(), body);
|
||||||
|
|
||||||
futures_util::pin_mut!(stream);
|
futures_util::pin_mut!(stream);
|
||||||
@@ -151,42 +363,112 @@ where
|
|||||||
fn map_request_streaming<B>(
|
fn map_request_streaming<B>(
|
||||||
&mut self,
|
&mut self,
|
||||||
request: http::Request<B>,
|
request: http::Request<B>,
|
||||||
) -> Request<Streaming<T::Decode>>
|
) -> Result<Request<Streaming<T::Decode>>, Status>
|
||||||
where
|
where
|
||||||
B: Body + Send + Sync + 'static,
|
B: Body + Send + Sync + 'static,
|
||||||
B::Error: Into<crate::Error> + Send,
|
B::Error: Into<crate::Error> + Send,
|
||||||
{
|
{
|
||||||
Request::from_http(request.map(|body| Streaming::new_request(self.codec.decoder(), body)))
|
#[cfg(feature = "compression")]
|
||||||
|
let encoding = self.request_encoding_if_supported(&request)?;
|
||||||
|
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
let request =
|
||||||
|
request.map(|body| Streaming::new_request(self.codec.decoder(), body, encoding));
|
||||||
|
|
||||||
|
#[cfg(not(feature = "compression"))]
|
||||||
|
let request = request.map(|body| Streaming::new_request(self.codec.decoder(), body));
|
||||||
|
|
||||||
|
Ok(Request::from_http(request))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_response<B>(
|
fn map_response<B>(
|
||||||
&mut self,
|
&mut self,
|
||||||
response: Result<crate::Response<B>, Status>,
|
response: Result<crate::Response<B>, Status>,
|
||||||
|
#[cfg(feature = "compression")] accept_encoding: Option<CompressionEncoding>,
|
||||||
|
#[cfg(feature = "compression")] compression_override: SingleMessageCompressionOverride,
|
||||||
) -> http::Response<BoxBody>
|
) -> http::Response<BoxBody>
|
||||||
where
|
where
|
||||||
B: TryStream<Ok = T::Encode, Error = Status> + Send + Sync + 'static,
|
B: TryStream<Ok = T::Encode, Error = Status> + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
match response {
|
let response = match response {
|
||||||
Ok(r) => {
|
Ok(r) => r,
|
||||||
let (mut parts, body) = r.into_http().into_parts();
|
Err(status) => return status.to_http(),
|
||||||
|
};
|
||||||
|
|
||||||
// Set the content type
|
let (mut parts, body) = response.into_http().into_parts();
|
||||||
parts.headers.insert(
|
|
||||||
http::header::CONTENT_TYPE,
|
|
||||||
http::header::HeaderValue::from_static("application/grpc"),
|
|
||||||
);
|
|
||||||
|
|
||||||
let body = encode_server(self.codec.encoder(), body.into_stream());
|
// Set the content type
|
||||||
|
parts.headers.insert(
|
||||||
|
http::header::CONTENT_TYPE,
|
||||||
|
http::header::HeaderValue::from_static("application/grpc"),
|
||||||
|
);
|
||||||
|
|
||||||
http::Response::from_parts(parts, BoxBody::new(body))
|
#[cfg(feature = "compression")]
|
||||||
}
|
if let Some(encoding) = accept_encoding {
|
||||||
Err(status) => status.to_http(),
|
// Set the content encoding
|
||||||
|
parts.headers.insert(
|
||||||
|
crate::codec::compression::ENCODING_HEADER,
|
||||||
|
encoding.into_header_value(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let body = encode_server(
|
||||||
|
self.codec.encoder(),
|
||||||
|
body.into_stream(),
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
accept_encoding,
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
compression_override,
|
||||||
|
);
|
||||||
|
|
||||||
|
http::Response::from_parts(parts, BoxBody::new(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
fn request_encoding_if_supported<B>(
|
||||||
|
&self,
|
||||||
|
request: &http::Request<B>,
|
||||||
|
) -> Result<Option<CompressionEncoding>, Status> {
|
||||||
|
CompressionEncoding::from_encoding_header(
|
||||||
|
request.headers(),
|
||||||
|
self.accept_compression_encodings,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: fmt::Debug> fmt::Debug for Grpc<T> {
|
impl<T: fmt::Debug> fmt::Debug for Grpc<T> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.debug_struct("Grpc").field("codec", &self.codec).finish()
|
let mut f = f.debug_struct("Grpc");
|
||||||
|
|
||||||
|
f.field("codec", &self.codec);
|
||||||
|
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
f.field(
|
||||||
|
"accept_compression_encodings",
|
||||||
|
&self.accept_compression_encodings,
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
f.field(
|
||||||
|
"send_compression_encodings",
|
||||||
|
&self.send_compression_encodings,
|
||||||
|
);
|
||||||
|
|
||||||
|
f.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
fn compression_override_from_response<B, E>(
|
||||||
|
res: &Result<crate::Response<B>, E>,
|
||||||
|
) -> SingleMessageCompressionOverride {
|
||||||
|
res.as_ref()
|
||||||
|
.ok()
|
||||||
|
.and_then(|response| {
|
||||||
|
response
|
||||||
|
.extensions()
|
||||||
|
.get::<SingleMessageCompressionOverride>()
|
||||||
|
.copied()
|
||||||
|
})
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user