diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index 10f854c46..702fba09f 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -6,7 +6,6 @@ - Add `HttpServer::{bind, listen}_auto_h2c()` method behind new `http2` crate feature. - Add `Resource::{get, post, etc...}` methods for more concisely adding routes that don't need additional guards. -- Add `Compress::with_predicate()` method for customizing when compression is applied. ### Changed diff --git a/actix-web/src/middleware/compress.rs b/actix-web/src/middleware/compress.rs index 76c3d968b..530b646c7 100644 --- a/actix-web/src/middleware/compress.rs +++ b/actix-web/src/middleware/compress.rs @@ -1,11 +1,9 @@ //! For middleware documentation, see [`Compress`]. use std::{ - fmt, future::Future, marker::PhantomData, pin::Pin, - rc::Rc, task::{Context, Poll}, }; @@ -27,8 +25,6 @@ use crate::{ Error, HttpMessage, HttpResponse, }; -type CompressPredicateFn = Rc) -> bool>; - /// Middleware for compressing response payloads. /// /// # Encoding Negotiation @@ -76,80 +72,9 @@ type CompressPredicateFn = Rc) -> bool>; /// ``` /// /// [feature flags]: ../index.html#crate-features -#[derive(Clone)] +#[derive(Debug, Clone, Default)] #[non_exhaustive] -pub struct Compress { - predicate: CompressPredicateFn, -} - -impl Compress { - /// Sets the `predicate` function to use when deciding if response should be compressed or not. - /// - /// The `predicate` function receives the response's current `Content-Type` header, if set. - /// Returning true from the predicate will instruct this middleware to compress the response. - /// - /// By default, video and image responses are unaffected (since they are typically compressed - /// already) and responses without a Content-Type header will be compressed. Custom predicate - /// functions should try to maintain these default rules. - /// - /// # Examples - /// - /// ``` - /// use actix_web::{App, middleware::Compress}; - /// - /// App::new() - /// .wrap(Compress::default().with_predicate(|content_type| { - /// // preserve that missing Content-Type header compresses content - /// let ct = match content_type.and_then(|ct| ct.to_str().ok()) { - /// None => return true, - /// Some(ct) => ct, - /// }; - /// - /// // parse Content-Type as MIME type - /// let ct_mime = match ct.parse::() { - /// Err(_) => return true, - /// Ok(mime) => mime, - /// }; - /// - /// // compress everything except HTML documents - /// ct_mime.subtype() != mime::HTML - /// })) - /// # ; - /// ``` - pub fn with_predicate( - self, - predicate: impl Fn(Option<&HeaderValue>) -> bool + 'static, - ) -> Self { - Self { - predicate: Rc::new(predicate), - } - } -} - -impl fmt::Debug for Compress { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Compress").finish_non_exhaustive() - } -} - -impl Default for Compress { - fn default() -> Self { - fn default_compress_predicate(content_type: Option<&HeaderValue>) -> bool { - match content_type { - None => true, - Some(hdr) => match hdr.to_str().ok().and_then(|hdr| hdr.parse::().ok()) { - Some(mime) if mime.type_().as_str() == "image" => false, - Some(mime) if mime.type_().as_str() == "video" => false, - _ => true, - }, - } - } - - Compress { - predicate: Rc::new(default_compress_predicate), - } - } -} +pub struct Compress; impl Transform for Compress where @@ -163,16 +88,12 @@ where type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { - ok(CompressMiddleware { - service, - predicate: Rc::clone(&self.predicate), - }) + ok(CompressMiddleware { service }) } } pub struct CompressMiddleware { service: S, - predicate: CompressPredicateFn, } impl Service for CompressMiddleware @@ -198,7 +119,6 @@ where return Either::left(CompressResponse { encoding: Encoding::identity(), fut: self.service.call(req), - predicate: Rc::clone(&self.predicate), _phantom: PhantomData, }) } @@ -226,7 +146,6 @@ where Some(encoding) => Either::left(CompressResponse { fut: self.service.call(req), encoding, - predicate: Rc::clone(&self.predicate), _phantom: PhantomData, }), } @@ -241,7 +160,6 @@ pin_project! { #[pin] fut: S::Future, encoding: Encoding, - predicate: CompressPredicateFn, _phantom: PhantomData, } } @@ -268,9 +186,20 @@ where Poll::Ready(Ok(resp.map_body(move |head, body| { let content_type = head.headers.get(header::CONTENT_TYPE); - let should_compress = (self.predicate)(content_type); + fn default_compress_predicate(content_type: Option<&HeaderValue>) -> bool { + match content_type { + None => true, + Some(hdr) => { + match hdr.to_str().ok().and_then(|hdr| hdr.parse::().ok()) { + Some(mime) if mime.type_().as_str() == "image" => false, + Some(mime) if mime.type_().as_str() == "video" => false, + _ => true, + } + } + } + } - let enc = if should_compress { + let enc = if default_compress_predicate(content_type) { enc } else { ContentEncoding::Identity @@ -340,10 +269,8 @@ mod tests { use std::collections::HashSet; // use static_assertions::assert_impl_all; - use super::*; - use crate::http::header::ContentType; - use crate::{middleware::DefaultHeaders, test, web, App}; + use crate::{http::header::ContentType, middleware::DefaultHeaders, test, web, App}; const HTML_DATA_PART: &str = "

hello world

return true, - Some(hdr) => hdr, - }; - - let mime = match hdr.parse::() { - Err(_) => return true, - Ok(mime) => mime, - }; - - // compress everything except HTML documents - mime.subtype() != mime::HTML - })) - .configure(configure_predicate_test), - ) - .await; - - let req = test::TestRequest::with_uri("/html") - .insert_header((header::ACCEPT_ENCODING, "gzip")); - let res = test::call_service(&app, req.to_request()).await; - assert_successful_identity_res_with_content_type(&res, "text/html"); - assert_eq!(test::read_body(res).await, HTML_DATA.as_bytes()); - - let req = test::TestRequest::with_uri("/image") - .insert_header((header::ACCEPT_ENCODING, "gzip")); - let res = test::call_service(&app, req.to_request()).await; - assert_successful_gzip_res_with_content_type(&res, "image/jpeg"); - assert_ne!(test::read_body(res).await, TEXT_DATA.as_bytes()); - } }