remove predicate options

This commit is contained in:
Rob Ede 2023-07-19 19:47:03 +01:00
parent f30422cf98
commit 4bba8f23b6
No known key found for this signature in database
GPG Key ID: 97C636207D3EF933
2 changed files with 21 additions and 131 deletions

View File

@ -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

View File

@ -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<dyn Fn(Option<&HeaderValue>) -> bool>;
/// Middleware for compressing response payloads.
///
/// # Encoding Negotiation
@ -76,80 +72,9 @@ type CompressPredicateFn = Rc<dyn Fn(Option<&HeaderValue>) -> 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::<mime::Mime>() {
/// 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::<Mime>().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<S, B> Transform<S, ServiceRequest> for Compress
where
@ -163,16 +88,12 @@ where
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ok(CompressMiddleware {
service,
predicate: Rc::clone(&self.predicate),
})
ok(CompressMiddleware { service })
}
}
pub struct CompressMiddleware<S> {
service: S,
predicate: CompressPredicateFn,
}
impl<S, B> Service<ServiceRequest> for CompressMiddleware<S>
@ -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<B>,
}
}
@ -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::<Mime>().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 = "<html><h1>hello world</h1></html";
const HTML_DATA: &str = const_str::repeat!(HTML_DATA_PART, 100);
@ -489,52 +416,16 @@ mod tests {
)
.await;
let req = test::TestRequest::with_uri("/html")
.insert_header((header::ACCEPT_ENCODING, "gzip"));
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_gzip_res_with_content_type(&res, "text/html");
assert_ne!(test::read_body(res).await, HTML_DATA.as_bytes());
let req = test::TestRequest::with_uri("/image")
.insert_header((header::ACCEPT_ENCODING, "gzip"));
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_identity_res_with_content_type(&res, "image/jpeg");
assert_eq!(test::read_body(res).await, TEXT_DATA.as_bytes());
}
#[actix_rt::test]
async fn prevents_compression_custom_predicate() {
let app = test::init_service(
App::new()
.wrap(Compress::default().with_predicate(|hdr| {
// preserve that missing CT header compresses content
let hdr = match hdr.and_then(|hdr| hdr.to_str().ok()) {
None => return true,
Some(hdr) => hdr,
};
let mime = match hdr.parse::<mime::Mime>() {
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());
}
}