summaryrefslogtreecommitdiff
path: root/vendor/tower-http/src/compression/layer.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/tower-http/src/compression/layer.rs')
-rw-r--r--vendor/tower-http/src/compression/layer.rs240
1 files changed, 240 insertions, 0 deletions
diff --git a/vendor/tower-http/src/compression/layer.rs b/vendor/tower-http/src/compression/layer.rs
new file mode 100644
index 00000000..5eca0c50
--- /dev/null
+++ b/vendor/tower-http/src/compression/layer.rs
@@ -0,0 +1,240 @@
+use super::{Compression, Predicate};
+use crate::compression::predicate::DefaultPredicate;
+use crate::compression::CompressionLevel;
+use crate::compression_utils::AcceptEncoding;
+use tower_layer::Layer;
+
+/// Compress response bodies of the underlying service.
+///
+/// This uses the `Accept-Encoding` header to pick an appropriate encoding and adds the
+/// `Content-Encoding` header to responses.
+///
+/// See the [module docs](crate::compression) for more details.
+#[derive(Clone, Debug, Default)]
+pub struct CompressionLayer<P = DefaultPredicate> {
+ accept: AcceptEncoding,
+ predicate: P,
+ quality: CompressionLevel,
+}
+
+impl<S, P> Layer<S> for CompressionLayer<P>
+where
+ P: Predicate,
+{
+ type Service = Compression<S, P>;
+
+ fn layer(&self, inner: S) -> Self::Service {
+ Compression {
+ inner,
+ accept: self.accept,
+ predicate: self.predicate.clone(),
+ quality: self.quality,
+ }
+ }
+}
+
+impl CompressionLayer {
+ /// Creates a new [`CompressionLayer`].
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ /// Sets whether to enable the gzip encoding.
+ #[cfg(feature = "compression-gzip")]
+ pub fn gzip(mut self, enable: bool) -> Self {
+ self.accept.set_gzip(enable);
+ self
+ }
+
+ /// Sets whether to enable the Deflate encoding.
+ #[cfg(feature = "compression-deflate")]
+ pub fn deflate(mut self, enable: bool) -> Self {
+ self.accept.set_deflate(enable);
+ self
+ }
+
+ /// Sets whether to enable the Brotli encoding.
+ #[cfg(feature = "compression-br")]
+ pub fn br(mut self, enable: bool) -> Self {
+ self.accept.set_br(enable);
+ self
+ }
+
+ /// Sets whether to enable the Zstd encoding.
+ #[cfg(feature = "compression-zstd")]
+ pub fn zstd(mut self, enable: bool) -> Self {
+ self.accept.set_zstd(enable);
+ self
+ }
+
+ /// Sets the compression quality.
+ pub fn quality(mut self, quality: CompressionLevel) -> Self {
+ self.quality = quality;
+ self
+ }
+
+ /// Disables the gzip encoding.
+ ///
+ /// This method is available even if the `gzip` crate feature is disabled.
+ pub fn no_gzip(mut self) -> Self {
+ self.accept.set_gzip(false);
+ self
+ }
+
+ /// Disables the Deflate encoding.
+ ///
+ /// This method is available even if the `deflate` crate feature is disabled.
+ pub fn no_deflate(mut self) -> Self {
+ self.accept.set_deflate(false);
+ self
+ }
+
+ /// Disables the Brotli encoding.
+ ///
+ /// This method is available even if the `br` crate feature is disabled.
+ pub fn no_br(mut self) -> Self {
+ self.accept.set_br(false);
+ self
+ }
+
+ /// Disables the Zstd encoding.
+ ///
+ /// This method is available even if the `zstd` crate feature is disabled.
+ pub fn no_zstd(mut self) -> Self {
+ self.accept.set_zstd(false);
+ self
+ }
+
+ /// Replace the current compression predicate.
+ ///
+ /// See [`Compression::compress_when`] for more details.
+ pub fn compress_when<C>(self, predicate: C) -> CompressionLayer<C>
+ where
+ C: Predicate,
+ {
+ CompressionLayer {
+ accept: self.accept,
+ predicate,
+ quality: self.quality,
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::test_helpers::Body;
+ use http::{header::ACCEPT_ENCODING, Request, Response};
+ use http_body_util::BodyExt;
+ use std::convert::Infallible;
+ use tokio::fs::File;
+ use tokio_util::io::ReaderStream;
+ use tower::{Service, ServiceBuilder, ServiceExt};
+
+ async fn handle(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
+ // Open the file.
+ let file = File::open("Cargo.toml").await.expect("file missing");
+ // Convert the file into a `Stream`.
+ let stream = ReaderStream::new(file);
+ // Convert the `Stream` into a `Body`.
+ let body = Body::from_stream(stream);
+ // Create response.
+ Ok(Response::new(body))
+ }
+
+ #[tokio::test]
+ async fn accept_encoding_configuration_works() -> Result<(), crate::BoxError> {
+ let deflate_only_layer = CompressionLayer::new()
+ .quality(CompressionLevel::Best)
+ .no_br()
+ .no_gzip();
+
+ let mut service = ServiceBuilder::new()
+ // Compress responses based on the `Accept-Encoding` header.
+ .layer(deflate_only_layer)
+ .service_fn(handle);
+
+ // Call the service with the deflate only layer
+ let request = Request::builder()
+ .header(ACCEPT_ENCODING, "gzip, deflate, br")
+ .body(Body::empty())?;
+
+ let response = service.ready().await?.call(request).await?;
+
+ assert_eq!(response.headers()["content-encoding"], "deflate");
+
+ // Read the body
+ let body = response.into_body();
+ let bytes = body.collect().await.unwrap().to_bytes();
+
+ let deflate_bytes_len = bytes.len();
+
+ let br_only_layer = CompressionLayer::new()
+ .quality(CompressionLevel::Best)
+ .no_gzip()
+ .no_deflate();
+
+ let mut service = ServiceBuilder::new()
+ // Compress responses based on the `Accept-Encoding` header.
+ .layer(br_only_layer)
+ .service_fn(handle);
+
+ // Call the service with the br only layer
+ let request = Request::builder()
+ .header(ACCEPT_ENCODING, "gzip, deflate, br")
+ .body(Body::empty())?;
+
+ let response = service.ready().await?.call(request).await?;
+
+ assert_eq!(response.headers()["content-encoding"], "br");
+
+ // Read the body
+ let body = response.into_body();
+ let bytes = body.collect().await.unwrap().to_bytes();
+
+ let br_byte_length = bytes.len();
+
+ // check the corresponding algorithms are actually used
+ // br should compresses better than deflate
+ assert!(br_byte_length < deflate_bytes_len * 9 / 10);
+
+ Ok(())
+ }
+
+ /// Test ensuring that zstd compression will not exceed an 8MiB window size; browsers do not
+ /// accept responses using 16MiB+ window sizes.
+ #[tokio::test]
+ async fn zstd_is_web_safe() -> Result<(), crate::BoxError> {
+ async fn zeroes(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
+ Ok(Response::new(Body::from(vec![0u8; 18_874_368])))
+ }
+ // zstd will (I believe) lower its window size if a larger one isn't beneficial and
+ // it knows the size of the input; use an 18MiB body to ensure it would want a
+ // >=16MiB window (though it might not be able to see the input size here).
+
+ let zstd_layer = CompressionLayer::new()
+ .quality(CompressionLevel::Best)
+ .no_br()
+ .no_deflate()
+ .no_gzip();
+
+ let mut service = ServiceBuilder::new().layer(zstd_layer).service_fn(zeroes);
+
+ let request = Request::builder()
+ .header(ACCEPT_ENCODING, "zstd")
+ .body(Body::empty())?;
+
+ let response = service.ready().await?.call(request).await?;
+
+ assert_eq!(response.headers()["content-encoding"], "zstd");
+
+ let body = response.into_body();
+ let bytes = body.collect().await?.to_bytes();
+ let mut dec = zstd::Decoder::new(&*bytes)?;
+ dec.window_log_max(23)?; // Limit window size accepted by decoder to 2 ^ 23 bytes (8MiB)
+
+ std::io::copy(&mut dec, &mut std::io::sink())?;
+
+ Ok(())
+ }
+}