Files
tonic/tests/compression/src/util.rs
T
David PedersenandGitHub 0583cff80f 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
2021-07-02 11:25:03 -04:00

140 lines
3.8 KiB
Rust

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()
}