1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-01-19 14:14:41 +01:00

feat(compress): use response content type to decide compress

This commit is contained in:
William R. Arellano 2023-03-14 21:55:15 -05:00
parent 2bfc170fb0
commit 14b09e35d1

View File

@ -1,6 +1,7 @@
//! For middleware documentation, see [`Compress`]. //! For middleware documentation, see [`Compress`].
use std::{ use std::{
fmt,
future::Future, future::Future,
marker::PhantomData, marker::PhantomData,
pin::Pin, pin::Pin,
@ -11,14 +12,14 @@ use actix_http::encoding::Encoder;
use actix_service::{Service, Transform}; use actix_service::{Service, Transform};
use actix_utils::future::{ok, Either, Ready}; use actix_utils::future::{ok, Either, Ready};
use futures_core::ready; use futures_core::ready;
use mime::Mime;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use pin_project_lite::pin_project; use pin_project_lite::pin_project;
use mime::Mime;
use crate::{ use crate::{
body::{EitherBody, MessageBody}, body::{EitherBody, MessageBody},
http::{ http::{
header::{self, AcceptEncoding, ContentType, Encoding, HeaderValue}, header::{self, AcceptEncoding, ContentEncoding, Encoding, HeaderValue},
StatusCode, StatusCode,
}, },
service::{ServiceRequest, ServiceResponse}, service::{ServiceRequest, ServiceResponse},
@ -72,16 +73,21 @@ use crate::{
/// ``` /// ```
/// ///
/// [feature flags]: ../index.html#crate-features /// [feature flags]: ../index.html#crate-features
#[derive(Debug, Clone)] #[derive(Clone)]
#[non_exhaustive] #[non_exhaustive]
pub struct Compress { pub struct Compress {
pub compress: fn(Mime) -> bool, pub compress: fn(&HeaderValue) -> bool,
} }
impl fmt::Debug for Compress {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Compress").finish()
}
}
impl Default for Compress { impl Default for Compress {
fn default() -> Self { fn default() -> Self {
Compress { Compress {
compress: |_| { true } compress: |_| false,
} }
} }
} }
@ -98,13 +104,16 @@ where
type Future = Ready<Result<Self::Transform, Self::InitError>>; type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future { fn new_transform(&self, service: S) -> Self::Future {
ok(CompressMiddleware { service, compress: self.compress }) ok(CompressMiddleware {
service,
compress: self.compress,
})
} }
} }
pub struct CompressMiddleware<S> { pub struct CompressMiddleware<S> {
service: S, service: S,
compress: fn(Mime) -> bool, compress: fn(&HeaderValue) -> bool,
} }
impl<S, B> Service<ServiceRequest> for CompressMiddleware<S> impl<S, B> Service<ServiceRequest> for CompressMiddleware<S>
@ -131,6 +140,7 @@ where
encoding: Encoding::identity(), encoding: Encoding::identity(),
fut: self.service.call(req), fut: self.service.call(req),
_phantom: PhantomData, _phantom: PhantomData,
compress: self.compress,
}) })
} }
@ -158,6 +168,7 @@ where
fut: self.service.call(req), fut: self.service.call(req),
encoding, encoding,
_phantom: PhantomData, _phantom: PhantomData,
compress: self.compress,
}), }),
} }
} }
@ -172,6 +183,7 @@ pin_project! {
fut: S::Future, fut: S::Future,
encoding: Encoding, encoding: Encoding,
_phantom: PhantomData<B>, _phantom: PhantomData<B>,
compress: fn(&HeaderValue) -> bool,
} }
} }
@ -182,8 +194,8 @@ where
{ {
type Output = Result<ServiceResponse<EitherBody<Encoder<B>>>, Error>; type Output = Result<ServiceResponse<EitherBody<Encoder<B>>>, Error>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project(); let this = self.as_mut().project();
match ready!(this.fut.poll(cx)) { match ready!(this.fut.poll(cx)) {
Ok(resp) => { Ok(resp) => {
@ -195,7 +207,19 @@ where
}; };
Poll::Ready(Ok(resp.map_body(move |head, body| { Poll::Ready(Ok(resp.map_body(move |head, body| {
let content_type = head.headers.get(header::CONTENT_TYPE);
let should_compress = content_type
.map(|value| (self.compress)(value))
.unwrap_or(true);
if should_compress {
EitherBody::left(Encoder::response(enc, head, body)) EitherBody::left(Encoder::response(enc, head, body))
} else {
EitherBody::left(Encoder::response(
ContentEncoding::Identity,
head,
body,
))
}
}))) })))
} }
@ -259,6 +283,7 @@ mod tests {
use std::collections::HashSet; use std::collections::HashSet;
use super::*; use super::*;
use crate::http::header::ContentType;
use crate::{middleware::DefaultHeaders, test, web, App}; use crate::{middleware::DefaultHeaders, test, web, App};
pub fn gzip_decode(bytes: impl AsRef<[u8]>) -> Vec<u8> { pub fn gzip_decode(bytes: impl AsRef<[u8]>) -> Vec<u8> {
@ -345,9 +370,9 @@ mod tests {
App::new().wrap(Compress::default()).route( App::new().wrap(Compress::default()).route(
"/image", "/image",
web::get().to(move || { web::get().to(move || {
let mut builder = HttpResponse::Ok(); let builder = HttpResponse::Ok()
builder.body(DATA); .insert_header(ContentType::jpeg())
builder.insert_header(ContentType::jpeg()); .body(DATA);
builder builder
}), }),
) )
@ -359,7 +384,10 @@ mod tests {
.to_request(); .to_request();
let res = test::call_service(&app, req).await; let res = test::call_service(&app, req).await;
assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.headers().get(header::CONTENT_TYPE).unwrap(), "gzip"); assert_eq!(
res.headers().get(header::CONTENT_TYPE).unwrap(),
"image/jpeg"
);
let bytes = test::read_body(res).await; let bytes = test::read_body(res).await;
assert_eq!(bytes, DATA.as_bytes()); assert_eq!(bytes, DATA.as_bytes());
} }