Skip to content

Commit

Permalink
fix: build error when using specific or no Cargo compression features (
Browse files Browse the repository at this point in the history
…#339)

also, if only one compression feature is
enabled (e.g. compression-gzip) then the preferred encoding gets
re-evaluated and assigned to that compression feature format  but only if it
was part of the `accept-encoding` request header value.

now the following scenarios will work:

[dependencies]
static-web-server = { version = "2.28.0", default-features = false }

or

 [dependencies]
static-web-server = { version = "2.28.0", default-features = false, features = ["compression-brotli"] }

in addition, some tracing logs are added to reflect the changes.
  • Loading branch information
joseluisq committed Apr 13, 2024
1 parent 69bfdd4 commit 183102d
Show file tree
Hide file tree
Showing 12 changed files with 432 additions and 70 deletions.
102 changes: 80 additions & 22 deletions src/compression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@

// Part of the file is borrowed from <https://github.com/seanmonstar/warp/pull/513>*

#[cfg(feature = "compression-brotli")]
#[cfg(any(feature = "compression", feature = "compression-brotli"))]
use async_compression::tokio::bufread::BrotliEncoder;
#[cfg(feature = "compression-deflate")]
#[cfg(any(feature = "compression", feature = "compression-deflate"))]
use async_compression::tokio::bufread::DeflateEncoder;
#[cfg(feature = "compression-gzip")]
#[cfg(any(feature = "compression", feature = "compression-gzip"))]
use async_compression::tokio::bufread::GzipEncoder;
#[cfg(feature = "compression-zstd")]
#[cfg(any(feature = "compression", feature = "compression-zstd"))]
use async_compression::tokio::bufread::ZstdEncoder;

use bytes::Bytes;
Expand Down Expand Up @@ -61,7 +61,7 @@ pub const TEXT_MIME_TYPES: [&str; 24] = [
];

/// Create a wrapping handler that compresses the Body of a [`hyper::Response`]
/// using `gzip`, `deflate`, `brotli` or `zstd` if is specified in the `Accept-Encoding` header, adding
/// using gzip, `deflate`, `brotli` or `zstd` if is specified in the `Accept-Encoding` header, adding
/// `content-encoding: <coding>` to the Response's [`HeaderMap`].
/// It also provides the ability to apply compression for text-based MIME types only.
pub fn auto(
Expand All @@ -76,6 +76,11 @@ pub fn auto(

// Compress response based on Accept-Encoding header
if let Some(encoding) = get_prefered_encoding(headers) {
tracing::trace!(
"prefered encoding selected from the accept-ecoding header: {:?}",
encoding
);

// Skip compression for non-text-based MIME types
if let Some(content_type) = resp.headers().typed_get::<ContentType>() {
let mime = Mime::from(content_type);
Expand All @@ -84,43 +89,48 @@ pub fn auto(
}
}

#[cfg(feature = "compression-gzip")]
#[cfg(any(feature = "compression", feature = "compression-gzip"))]
if encoding == ContentCoding::GZIP {
let (head, body) = resp.into_parts();
return Ok(gzip(head, body.into()));
}

#[cfg(feature = "compression-deflate")]
#[cfg(any(feature = "compression", feature = "compression-deflate"))]
if encoding == ContentCoding::DEFLATE {
let (head, body) = resp.into_parts();
return Ok(deflate(head, body.into()));
}

#[cfg(feature = "compression-brotli")]
#[cfg(any(feature = "compression", feature = "compression-brotli"))]
if encoding == ContentCoding::BROTLI {
let (head, body) = resp.into_parts();
return Ok(brotli(head, body.into()));
}

#[cfg(feature = "compression-zstd")]
#[cfg(any(feature = "compression", feature = "compression-zstd"))]
if encoding == ContentCoding::ZSTD {
let (head, body) = resp.into_parts();
return Ok(zstd(head, body.into()));
}

tracing::trace!("no compression feature matched the prefered encoding, probably not enabled or unsupported");
}

Ok(resp)
}

/// Create a wrapping handler that compresses the Body of a [`Response`].
/// using gzip, adding `content-encoding: gzip` to the Response's [`HeaderMap`].
#[cfg(feature = "compression-gzip")]
#[cfg_attr(docsrs, doc(cfg(feature = "compression-gzip")))]
#[cfg(any(feature = "compression", feature = "compression-gzip"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "compression", feature = "compression-gzip")))
)]
pub fn gzip(
mut head: http::response::Parts,
body: CompressableBody<Body, hyper::Error>,
) -> Response<Body> {
tracing::trace!("compressing response body on the fly using gzip");
tracing::trace!("compressing response body on the fly using GZIP");

let body = Body::wrap_stream(ReaderStream::new(GzipEncoder::new(StreamReader::new(body))));
let header = create_encoding_header(head.headers.remove(CONTENT_ENCODING), ContentCoding::GZIP);
Expand All @@ -131,13 +141,16 @@ pub fn gzip(

/// Create a wrapping handler that compresses the Body of a [`Response`].
/// using deflate, adding `content-encoding: deflate` to the Response's [`HeaderMap`].
#[cfg(feature = "compression-deflate")]
#[cfg_attr(docsrs, doc(cfg(feature = "compression-deflate")))]
#[cfg(any(feature = "compression", feature = "compression-deflate"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "compression", feature = "compression-deflate")))
)]
pub fn deflate(
mut head: http::response::Parts,
body: CompressableBody<Body, hyper::Error>,
) -> Response<Body> {
tracing::trace!("compressing response body on the fly using deflate");
tracing::trace!("compressing response body on the fly using DEFLATE");

let body = Body::wrap_stream(ReaderStream::new(DeflateEncoder::new(StreamReader::new(
body,
Expand All @@ -153,13 +166,16 @@ pub fn deflate(

/// Create a wrapping handler that compresses the Body of a [`Response`].
/// using brotli, adding `content-encoding: br` to the Response's [`HeaderMap`].
#[cfg(feature = "compression-brotli")]
#[cfg_attr(docsrs, doc(cfg(feature = "compression-brotli")))]
#[cfg(any(feature = "compression", feature = "compression-brotli"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "compression", feature = "compression-brotli")))
)]
pub fn brotli(
mut head: http::response::Parts,
body: CompressableBody<Body, hyper::Error>,
) -> Response<Body> {
tracing::trace!("compressing response body on the fly using brotli");
tracing::trace!("compressing response body on the fly using BROTLI");

let body = Body::wrap_stream(ReaderStream::new(BrotliEncoder::new(StreamReader::new(
body,
Expand All @@ -173,13 +189,16 @@ pub fn brotli(

/// Create a wrapping handler that compresses the Body of a [`Response`].
/// using zstd, adding `content-encoding: zstd` to the Response's [`HeaderMap`].
#[cfg(feature = "compression-zstd")]
#[cfg_attr(docsrs, doc(cfg(feature = "compression-zstd")))]
#[cfg(any(feature = "compression", feature = "compression-zstd"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "compression", feature = "compression-zstd")))
)]
pub fn zstd(
mut head: http::response::Parts,
body: CompressableBody<Body, hyper::Error>,
) -> Response<Body> {
tracing::trace!("compressing response body on the fly using zstd");
tracing::trace!("compressing response body on the fly using ZSTD");

let body = Body::wrap_stream(ReaderStream::new(ZstdEncoder::new(StreamReader::new(body))));
let header = create_encoding_header(head.headers.remove(CONTENT_ENCODING), ContentCoding::ZSTD);
Expand All @@ -203,7 +222,46 @@ pub fn create_encoding_header(existing: Option<HeaderValue>, coding: ContentCodi
#[inline(always)]
pub fn get_prefered_encoding(headers: &HeaderMap<HeaderValue>) -> Option<ContentCoding> {
if let Some(ref accept_encoding) = headers.typed_get::<AcceptEncoding>() {
return accept_encoding.prefered_encoding();
tracing::trace!("request with accept-ecoding header: {:?}", accept_encoding);

let encoding = accept_encoding.prefered_encoding();
if let Some(prefered_enc) = encoding {
let mut feature_formats = Vec::<ContentCoding>::with_capacity(5);
if cfg!(feature = "compression-deflate") {
feature_formats.push(ContentCoding::DEFLATE);
}
if cfg!(feature = "compression-gzip") {
feature_formats.push(ContentCoding::GZIP);
}
if cfg!(feature = "compression-brotli") {
feature_formats.push(ContentCoding::BROTLI);
}
if cfg!(feature = "compression-zstd") {
feature_formats.push(ContentCoding::ZSTD);
}

// If there is only one Cargo compression feature enabled
// then re-evaluate the prefered encoding and return
// that feature compression algorithm as `ContentCoding` only
// if was contained in the `AcceptEncoding` value.
if feature_formats.len() == 1 {
let feature_enc = *feature_formats.first().unwrap();
if feature_enc != prefered_enc
&& accept_encoding
.sorted_encodings()
.any(|enc| enc == feature_enc)
{
tracing::trace!(
"prefered encoding {:?} is re-evalated to {:?} because is the only compression feature enabled that matches the `accept-encoding` header",
prefered_enc,
feature_enc
);
return Some(feature_enc);
}
}

return encoding;
}
}
None
}
Expand Down
6 changes: 3 additions & 3 deletions src/compression_static.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@ pub async fn precompressed_variant<'a>(
// Determine prefered-encoding extension if available
let comp_ext = match compression::get_prefered_encoding(headers) {
// https://zlib.net/zlib_faq.html#faq39
#[cfg(feature = "compression-gzip")]
#[cfg(any(feature = "compression", feature = "compression-gzip"))]
Some(ContentCoding::GZIP | ContentCoding::DEFLATE) => "gz",
// https://peazip.github.io/brotli-compressed-file-format.html
#[cfg(feature = "compression-brotli")]
#[cfg(any(feature = "compression", feature = "compression-brotli"))]
Some(ContentCoding::BROTLI) => "br",
// https://datatracker.ietf.org/doc/html/rfc8878
#[cfg(feature = "compression-zstd")]
#[cfg(any(feature = "compression", feature = "compression-zstd"))]
Some(ContentCoding::ZSTD) => "zst",
_ => {
tracing::trace!(
Expand Down
40 changes: 35 additions & 5 deletions src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ use headers::{ContentType, HeaderMap, HeaderMapExt, HeaderValue};
use hyper::{Body, Request, Response, StatusCode};
use std::{future::Future, net::IpAddr, net::SocketAddr, path::PathBuf, sync::Arc};

#[cfg(feature = "compression")]
#[cfg(any(
feature = "compression",
feature = "compression-gzip",
feature = "compression-brotli",
feature = "compression-zstd",
feature = "compression-deflate"
))]
use crate::compression;

#[cfg(feature = "basic-auth")]
Expand Down Expand Up @@ -457,7 +463,13 @@ impl RequestHandler {
}

// Compression content encoding varies so use a `Vary` header
#[cfg(feature = "compression")]
#[cfg(any(
feature = "compression",
feature = "compression-gzip",
feature = "compression-brotli",
feature = "compression-zstd",
feature = "compression-deflate"
))]
if self.opts.compression || compression_static {
resp.headers_mut().append(
hyper::header::VARY,
Expand All @@ -466,7 +478,13 @@ impl RequestHandler {
}

// Auto compression based on the `Accept-Encoding` header
#[cfg(feature = "compression")]
#[cfg(any(
feature = "compression",
feature = "compression-gzip",
feature = "compression-brotli",
feature = "compression-zstd",
feature = "compression-deflate"
))]
if self.opts.compression && !_is_precompressed {
resp = match compression::auto(method, headers, resp) {
Ok(res) => res,
Expand Down Expand Up @@ -526,7 +544,13 @@ impl RequestHandler {
}

// Compression content encoding varies so use a `Vary` header
#[cfg(feature = "compression")]
#[cfg(any(
feature = "compression",
feature = "compression-gzip",
feature = "compression-brotli",
feature = "compression-zstd",
feature = "compression-deflate"
))]
if self.opts.compression || compression_static {
resp.headers_mut().append(
hyper::header::VARY,
Expand All @@ -535,7 +559,13 @@ impl RequestHandler {
}

// Auto compression based on the `Accept-Encoding` header
#[cfg(feature = "compression")]
#[cfg(any(
feature = "compression",
feature = "compression-gzip",
feature = "compression-brotli",
feature = "compression-zstd",
feature = "compression-deflate"
))]
if self.opts.compression {
resp = match compression::auto(method, headers, resp) {
Ok(res) => res,
Expand Down
38 changes: 34 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,41 @@ extern crate serde;
#[cfg(feature = "basic-auth")]
#[cfg_attr(docsrs, doc(cfg(feature = "basic-auth")))]
pub mod basic_auth;
#[cfg(feature = "compression")]
#[cfg_attr(docsrs, doc(cfg(feature = "compression")))]
#[cfg(any(
feature = "compression",
feature = "compression-gzip",
feature = "compression-brotli",
feature = "compression-zstd",
feature = "compression-deflate"
))]
#[cfg_attr(
docsrs,
doc(cfg(any(
feature = "compression",
feature = "compression-gzip",
feature = "compression-brotli",
feature = "compression-zstd",
feature = "compression-deflate"
)))
)]
pub mod compression;
#[cfg(feature = "compression")]
#[cfg_attr(docsrs, doc(cfg(feature = "compression")))]
#[cfg(any(
feature = "compression",
feature = "compression-gzip",
feature = "compression-brotli",
feature = "compression-zstd",
feature = "compression-deflate"
))]
#[cfg_attr(
docsrs,
doc(cfg(any(
feature = "compression",
feature = "compression-gzip",
feature = "compression-brotli",
feature = "compression-zstd",
feature = "compression-deflate"
)))
)]
pub mod compression_static;
pub mod control_headers;
pub mod cors;
Expand Down
Loading

0 comments on commit 183102d

Please sign in to comment.