remove BodyEncoding trait

This commit is contained in:
Rob Ede 2022-01-03 17:37:23 +00:00
parent 19a46e3925
commit 9dabaafb12
No known key found for this signature in database
GPG Key ID: 97C636207D3EF933
14 changed files with 259 additions and 332 deletions

View File

@ -597,7 +597,8 @@ mod tests {
.to_request(); .to_request();
let res = test::call_service(&srv, request).await; let res = test::call_service(&srv, request).await;
assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.status(), StatusCode::OK);
assert!(!res.headers().contains_key(header::CONTENT_ENCODING)); assert!(res.headers().contains_key(header::CONTENT_ENCODING));
assert!(!test::read_body(res).await.is_empty());
} }
#[actix_rt::test] #[actix_rt::test]

View File

@ -9,14 +9,11 @@ use std::{
use actix_service::{Service, ServiceFactory}; use actix_service::{Service, ServiceFactory};
use actix_web::{ use actix_web::{
body::{self, BoxBody, SizedStream}, body::{self, BoxBody, SizedStream},
dev::{ dev::{AppService, HttpServiceFactory, ResourceDef, ServiceRequest, ServiceResponse},
AppService, BodyEncoding, HttpServiceFactory, ResourceDef, ServiceRequest,
ServiceResponse,
},
http::{ http::{
header::{ header::{
self, Charset, ContentDisposition, ContentEncoding, DispositionParam, self, Charset, ContentDisposition, ContentEncoding, DispositionParam,
DispositionType, ExtendedValue, DispositionType, ExtendedValue, HeaderValue,
}, },
StatusCode, StatusCode,
}, },
@ -224,7 +221,6 @@ impl NamedFile {
}) })
} }
#[cfg(not(feature = "experimental-io-uring"))]
/// Attempts to open a file in read-only mode. /// Attempts to open a file in read-only mode.
/// ///
/// # Examples /// # Examples
@ -232,6 +228,7 @@ impl NamedFile {
/// use actix_files::NamedFile; /// use actix_files::NamedFile;
/// let file = NamedFile::open("foo.txt"); /// let file = NamedFile::open("foo.txt");
/// ``` /// ```
#[cfg(not(feature = "experimental-io-uring"))]
pub fn open<P: AsRef<Path>>(path: P) -> io::Result<NamedFile> { pub fn open<P: AsRef<Path>>(path: P) -> io::Result<NamedFile> {
let file = File::open(&path)?; let file = File::open(&path)?;
Self::from_file(file, path) Self::from_file(file, path)
@ -295,23 +292,21 @@ impl NamedFile {
self self
} }
/// Set the MIME Content-Type for serving this file. By default /// Set the MIME Content-Type for serving this file. By default the Content-Type is inferred
/// the Content-Type is inferred from the filename extension. /// from the filename extension.
#[inline] #[inline]
pub fn set_content_type(mut self, mime_type: mime::Mime) -> Self { pub fn set_content_type(mut self, mime_type: mime::Mime) -> Self {
self.content_type = mime_type; self.content_type = mime_type;
self self
} }
/// Set the Content-Disposition for serving this file. This allows /// Set the Content-Disposition for serving this file. This allows changing the
/// changing the inline/attachment disposition as well as the filename /// `inline/attachment` disposition as well as the filename sent to the peer.
/// sent to the peer.
/// ///
/// By default the disposition is `inline` for `text/*`, `image/*`, `video/*` and /// By default the disposition is `inline` for `text/*`, `image/*`, `video/*` and
/// `application/{javascript, json, wasm}` mime types, and `attachment` otherwise, /// `application/{javascript, json, wasm}` mime types, and `attachment` otherwise, and the
/// and the filename is taken from the path provided in the `open` method /// filename is taken from the path provided in the `open` method after converting it to UTF-8
/// after converting it to UTF-8 using. /// (using `to_string_lossy`).
/// [`std::ffi::OsStr::to_string_lossy`]
#[inline] #[inline]
pub fn set_content_disposition(mut self, cd: header::ContentDisposition) -> Self { pub fn set_content_disposition(mut self, cd: header::ContentDisposition) -> Self {
self.content_disposition = cd; self.content_disposition = cd;
@ -337,7 +332,7 @@ impl NamedFile {
self self
} }
/// Specifies whether to use ETag or not. /// Specifies whether to return `ETag` header in response.
/// ///
/// Default is true. /// Default is true.
#[inline] #[inline]
@ -346,7 +341,7 @@ impl NamedFile {
self self
} }
/// Specifies whether to use Last-Modified or not. /// Specifies whether to return `Last-Modified` header in response.
/// ///
/// Default is true. /// Default is true.
#[inline] #[inline]
@ -364,7 +359,7 @@ impl NamedFile {
self self
} }
/// Creates a etag in a format is similar to Apache's. /// Creates an `ETag` in a format is similar to Apache's.
pub(crate) fn etag(&self) -> Option<header::EntityTag> { pub(crate) fn etag(&self) -> Option<header::EntityTag> {
self.modified.as_ref().map(|mtime| { self.modified.as_ref().map(|mtime| {
let ino = { let ino = {
@ -405,12 +400,13 @@ impl NamedFile {
if self.status_code != StatusCode::OK { if self.status_code != StatusCode::OK {
let mut res = HttpResponse::build(self.status_code); let mut res = HttpResponse::build(self.status_code);
if self.flags.contains(Flags::PREFER_UTF8) { let ct = if self.flags.contains(Flags::PREFER_UTF8) {
let ct = equiv_utf8_text(self.content_type.clone()); equiv_utf8_text(self.content_type.clone())
res.insert_header((header::CONTENT_TYPE, ct.to_string()));
} else { } else {
res.insert_header((header::CONTENT_TYPE, self.content_type.to_string())); self.content_type
} };
res.insert_header((header::CONTENT_TYPE, ct.to_string()));
if self.flags.contains(Flags::CONTENT_DISPOSITION) { if self.flags.contains(Flags::CONTENT_DISPOSITION) {
res.insert_header(( res.insert_header((
@ -420,7 +416,7 @@ impl NamedFile {
} }
if let Some(current_encoding) = self.encoding { if let Some(current_encoding) = self.encoding {
res.encode_with(current_encoding); res.insert_header((header::CONTENT_ENCODING, current_encoding.as_str()));
} }
let reader = chunked::new_chunked_read(self.md.len(), 0, self.file); let reader = chunked::new_chunked_read(self.md.len(), 0, self.file);
@ -478,12 +474,13 @@ impl NamedFile {
let mut res = HttpResponse::build(self.status_code); let mut res = HttpResponse::build(self.status_code);
if self.flags.contains(Flags::PREFER_UTF8) { let ct = if self.flags.contains(Flags::PREFER_UTF8) {
let ct = equiv_utf8_text(self.content_type.clone()); equiv_utf8_text(self.content_type.clone())
res.insert_header((header::CONTENT_TYPE, ct.to_string()));
} else { } else {
res.insert_header((header::CONTENT_TYPE, self.content_type.to_string())); self.content_type
} };
res.insert_header((header::CONTENT_TYPE, ct.to_string()));
if self.flags.contains(Flags::CONTENT_DISPOSITION) { if self.flags.contains(Flags::CONTENT_DISPOSITION) {
res.insert_header(( res.insert_header((
@ -492,9 +489,8 @@ impl NamedFile {
)); ));
} }
// default compressing
if let Some(current_encoding) = self.encoding { if let Some(current_encoding) = self.encoding {
res.encode_with(current_encoding); res.insert_header((header::CONTENT_ENCODING, current_encoding.as_str()));
} }
if let Some(lm) = last_modified { if let Some(lm) = last_modified {
@ -517,7 +513,12 @@ impl NamedFile {
length = ranges[0].length; length = ranges[0].length;
offset = ranges[0].start; offset = ranges[0].start;
res.encode_with(ContentEncoding::Identity); // don't allow compression middleware to modify partial content
res.insert_header((
header::CONTENT_ENCODING,
HeaderValue::from_static("identity"),
));
res.insert_header(( res.insert_header((
header::CONTENT_RANGE, header::CONTENT_RANGE,
format!("bytes {}-{}/{}", offset, offset + length - 1, self.md.len()), format!("bytes {}-{}/{}", offset, offset + length - 1, self.md.len()),

View File

@ -6,7 +6,7 @@ use ahash::AHashMap;
use http::header::{HeaderName, HeaderValue}; use http::header::{HeaderName, HeaderValue};
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
use crate::header::AsHeaderName; use super::AsHeaderName;
/// A multi-map of HTTP headers. /// A multi-map of HTTP headers.
/// ///

View File

@ -50,10 +50,10 @@ pub use self::utils::{
/// An interface for types that already represent a valid header. /// An interface for types that already represent a valid header.
pub trait Header: TryIntoHeaderValue { pub trait Header: TryIntoHeaderValue {
/// Returns the name of the header field /// Returns the name of the header field.
fn name() -> HeaderName; fn name() -> HeaderName;
/// Parse a header /// Parse the header from a HTTP message.
fn parse<M: HttpMessage>(msg: &M) -> Result<Self, ParseError>; fn parse<M: HttpMessage>(msg: &M) -> Result<Self, ParseError>;
} }

View File

@ -68,6 +68,7 @@ impl ContentEncoding {
} }
impl Default for ContentEncoding { impl Default for ContentEncoding {
#[inline]
fn default() -> Self { fn default() -> Self {
Self::Identity Self::Identity
} }

View File

@ -102,12 +102,14 @@ actix-utils = "3.0.0"
actix-web = { version = "4.0.0-beta.18", features = ["openssl"] } actix-web = { version = "4.0.0-beta.18", features = ["openssl"] }
brotli2 = "0.3.2" brotli2 = "0.3.2"
const-str = "0.3"
env_logger = "0.9" env_logger = "0.9"
flate2 = "1.0.13" flate2 = "1.0.13"
futures-util = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false }
static_assertions = "1.1" static_assertions = "1.1"
rcgen = "0.8" rcgen = "0.8"
rustls-pemfile = "0.2" rustls-pemfile = "0.2"
zstd = "0.9"
[[example]] [[example]]
name = "client" name = "client"

View File

@ -1,5 +1,6 @@
use std::{ use std::{
collections::HashMap, collections::HashMap,
convert::Infallible,
io::{Read, Write}, io::{Read, Write},
net::{IpAddr, Ipv4Addr}, net::{IpAddr, Ipv4Addr},
sync::{ sync::{
@ -15,43 +16,16 @@ use cookie::Cookie;
use futures_util::stream; use futures_util::stream;
use rand::Rng; use rand::Rng;
#[cfg(feature = "compress-brotli")] use actix_http::{HttpService, StatusCode};
use brotli2::write::BrotliEncoder;
#[cfg(feature = "compress-gzip")]
use flate2::{read::GzDecoder, write::GzEncoder, Compression};
use actix_http::{ContentEncoding, HttpService, StatusCode};
use actix_http_test::test_server; use actix_http_test::test_server;
use actix_service::{fn_service, map_config, ServiceFactoryExt as _}; use actix_service::{fn_service, map_config, ServiceFactoryExt as _};
use actix_web::{ use actix_web::{dev::AppConfig, http::header, web, App, Error, HttpRequest, HttpResponse};
dev::{AppConfig, BodyEncoding},
http::header,
web, App, Error, HttpRequest, HttpResponse,
};
use awc::error::{JsonPayloadError, PayloadError, SendRequestError}; use awc::error::{JsonPayloadError, PayloadError, SendRequestError};
const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ mod utils;
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \ const S: &str = "Hello World ";
Hello World Hello World Hello World Hello World Hello World \ const STR: &str = const_str::repeat!(S, 100);
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World";
#[actix_rt::test] #[actix_rt::test]
async fn test_simple() { async fn test_simple() {
@ -471,15 +445,12 @@ async fn test_no_decompress() {
let srv = actix_test::start(|| { let srv = actix_test::start(|| {
App::new() App::new()
.wrap(actix_web::middleware::Compress::default()) .wrap(actix_web::middleware::Compress::default())
.service(web::resource("/").route(web::to(|| { .service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))))
let mut res = HttpResponse::Ok().body(STR);
res.encode_with(header::ContentEncoding::Gzip);
res
})))
}); });
let mut res = awc::Client::new() let mut res = awc::Client::new()
.get(srv.url("/")) .get(srv.url("/"))
.insert_header((header::ACCEPT_ENCODING, "gzip"))
.no_decompress() .no_decompress()
.send() .send()
.await .await
@ -488,15 +459,12 @@ async fn test_no_decompress() {
// read response // read response
let bytes = res.body().await.unwrap(); let bytes = res.body().await.unwrap();
assert_eq!(utils::gzip::decode(bytes), STR.as_bytes());
let mut e = GzDecoder::new(&bytes[..]);
let mut dec = Vec::new();
e.read_to_end(&mut dec).unwrap();
assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref()));
// POST // POST
let mut res = awc::Client::new() let mut res = awc::Client::new()
.post(srv.url("/")) .post(srv.url("/"))
.insert_header((header::ACCEPT_ENCODING, "gzip"))
.no_decompress() .no_decompress()
.send() .send()
.await .await
@ -504,10 +472,7 @@ async fn test_no_decompress() {
assert!(res.status().is_success()); assert!(res.status().is_success());
let bytes = res.body().await.unwrap(); let bytes = res.body().await.unwrap();
let mut e = GzDecoder::new(&bytes[..]); assert_eq!(utils::gzip::decode(bytes), STR.as_bytes());
let mut dec = Vec::new();
e.read_to_end(&mut dec).unwrap();
assert_eq!(Bytes::from(dec), Bytes::from_static(STR.as_ref()));
} }
#[cfg(feature = "compress-gzip")] #[cfg(feature = "compress-gzip")]
@ -515,13 +480,9 @@ async fn test_no_decompress() {
async fn test_client_gzip_encoding() { async fn test_client_gzip_encoding() {
let srv = actix_test::start(|| { let srv = actix_test::start(|| {
App::new().service(web::resource("/").route(web::to(|| { App::new().service(web::resource("/").route(web::to(|| {
let mut e = GzEncoder::new(Vec::new(), Compression::default());
e.write_all(STR.as_ref()).unwrap();
let data = e.finish().unwrap();
HttpResponse::Ok() HttpResponse::Ok()
.insert_header(("content-encoding", "gzip")) .insert_header(header::ContentEncoding::Gzip)
.body(data) .body(utils::gzip::encode(STR))
}))) })))
}); });
@ -531,7 +492,7 @@ async fn test_client_gzip_encoding() {
// read response // read response
let bytes = response.body().await.unwrap(); let bytes = response.body().await.unwrap();
assert_eq!(bytes, Bytes::from_static(STR.as_ref())); assert_eq!(bytes, STR);
} }
#[cfg(feature = "compress-gzip")] #[cfg(feature = "compress-gzip")]
@ -539,13 +500,9 @@ async fn test_client_gzip_encoding() {
async fn test_client_gzip_encoding_large() { async fn test_client_gzip_encoding_large() {
let srv = actix_test::start(|| { let srv = actix_test::start(|| {
App::new().service(web::resource("/").route(web::to(|| { App::new().service(web::resource("/").route(web::to(|| {
let mut e = GzEncoder::new(Vec::new(), Compression::default());
e.write_all(STR.repeat(10).as_ref()).unwrap();
let data = e.finish().unwrap();
HttpResponse::Ok() HttpResponse::Ok()
.insert_header(("content-encoding", "gzip")) .insert_header(header::ContentEncoding::Gzip)
.body(data) .body(utils::gzip::encode(STR.repeat(10)))
}))) })))
}); });
@ -555,7 +512,7 @@ async fn test_client_gzip_encoding_large() {
// read response // read response
let bytes = response.body().await.unwrap(); let bytes = response.body().await.unwrap();
assert_eq!(bytes, Bytes::from(STR.repeat(10))); assert_eq!(bytes, STR.repeat(10));
} }
#[cfg(feature = "compress-gzip")] #[cfg(feature = "compress-gzip")]
@ -569,12 +526,9 @@ async fn test_client_gzip_encoding_large_random() {
let srv = actix_test::start(|| { let srv = actix_test::start(|| {
App::new().service(web::resource("/").route(web::to(|data: Bytes| { App::new().service(web::resource("/").route(web::to(|data: Bytes| {
let mut e = GzEncoder::new(Vec::new(), Compression::default());
e.write_all(&data).unwrap();
let data = e.finish().unwrap();
HttpResponse::Ok() HttpResponse::Ok()
.insert_header(("content-encoding", "gzip")) .insert_header(header::ContentEncoding::Gzip)
.body(data) .body(utils::gzip::encode(data))
}))) })))
}); });
@ -584,7 +538,7 @@ async fn test_client_gzip_encoding_large_random() {
// read response // read response
let bytes = response.body().await.unwrap(); let bytes = response.body().await.unwrap();
assert_eq!(bytes, Bytes::from(data)); assert_eq!(bytes, data);
} }
#[cfg(feature = "compress-brotli")] #[cfg(feature = "compress-brotli")]
@ -592,12 +546,9 @@ async fn test_client_gzip_encoding_large_random() {
async fn test_client_brotli_encoding() { async fn test_client_brotli_encoding() {
let srv = actix_test::start(|| { let srv = actix_test::start(|| {
App::new().service(web::resource("/").route(web::to(|data: Bytes| { App::new().service(web::resource("/").route(web::to(|data: Bytes| {
let mut e = BrotliEncoder::new(Vec::new(), 5);
e.write_all(&data).unwrap();
let data = e.finish().unwrap();
HttpResponse::Ok() HttpResponse::Ok()
.insert_header(("content-encoding", "br")) .insert_header(("content-encoding", "br"))
.body(data) .body(utils::brotli::encode(data))
}))) })))
}); });
@ -621,12 +572,9 @@ async fn test_client_brotli_encoding_large_random() {
let srv = actix_test::start(|| { let srv = actix_test::start(|| {
App::new().service(web::resource("/").route(web::to(|data: Bytes| { App::new().service(web::resource("/").route(web::to(|data: Bytes| {
let mut e = BrotliEncoder::new(Vec::new(), 5);
e.write_all(&data).unwrap();
let data = e.finish().unwrap();
HttpResponse::Ok() HttpResponse::Ok()
.insert_header(("content-encoding", "br")) .insert_header(header::ContentEncoding::Brotli)
.body(data) .body(utils::brotli::encode(&data))
}))) })))
}); });
@ -636,27 +584,25 @@ async fn test_client_brotli_encoding_large_random() {
// read response // read response
let bytes = response.body().await.unwrap(); let bytes = response.body().await.unwrap();
assert_eq!(bytes.len(), data.len()); assert_eq!(bytes, data);
assert_eq!(bytes, Bytes::from(data));
} }
#[actix_rt::test] #[actix_rt::test]
async fn test_client_deflate_encoding() { async fn test_client_deflate_encoding() {
let srv = actix_test::start(|| { let srv = actix_test::start(|| {
App::new().default_service(web::to(|body: Bytes| { App::new().default_service(web::to(|body: Bytes| HttpResponse::Ok().body(body)))
HttpResponse::Ok()
.encode_with(ContentEncoding::Brotli)
.body(body)
}))
}); });
let req = srv.post("/").send_body(STR); let req = srv
.post("/")
.insert_header((header::ACCEPT_ENCODING, "gzip"))
.send_body(STR);
let mut res = req.await.unwrap(); let mut res = req.await.unwrap();
assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.status(), StatusCode::OK);
let bytes = res.body().await.unwrap(); let bytes = res.body().await.unwrap();
assert_eq!(bytes, Bytes::from_static(STR.as_ref())); assert_eq!(bytes, STR);
} }
#[actix_rt::test] #[actix_rt::test]
@ -668,14 +614,13 @@ async fn test_client_deflate_encoding_large_random() {
.collect::<String>(); .collect::<String>();
let srv = actix_test::start(|| { let srv = actix_test::start(|| {
App::new().default_service(web::to(|body: Bytes| { App::new().default_service(web::to(|body: Bytes| HttpResponse::Ok().body(body)))
HttpResponse::Ok()
.encode_with(ContentEncoding::Brotli)
.body(body)
}))
}); });
let req = srv.post("/").send_body(data.clone()); let req = srv
.post("/")
.insert_header((header::ACCEPT_ENCODING, "br"))
.send_body(data.clone());
let mut res = req.await.unwrap(); let mut res = req.await.unwrap();
let bytes = res.body().await.unwrap(); let bytes = res.body().await.unwrap();
@ -688,15 +633,16 @@ async fn test_client_deflate_encoding_large_random() {
async fn test_client_streaming_explicit() { async fn test_client_streaming_explicit() {
let srv = actix_test::start(|| { let srv = actix_test::start(|| {
App::new().default_service(web::to(|body: web::Payload| { App::new().default_service(web::to(|body: web::Payload| {
HttpResponse::Ok() HttpResponse::Ok().streaming(body)
.encode_with(ContentEncoding::Identity)
.streaming(body)
})) }))
}); });
let body = let body =
stream::once(async { Ok::<_, actix_http::Error>(Bytes::from_static(STR.as_bytes())) }); stream::once(async { Ok::<_, actix_http::Error>(Bytes::from_static(STR.as_bytes())) });
let req = srv.post("/").send_stream(Box::pin(body)); let req = srv
.post("/")
.insert_header((header::ACCEPT_ENCODING, "identity"))
.send_stream(Box::pin(body));
let mut res = req.await.unwrap(); let mut res = req.await.unwrap();
assert!(res.status().is_success()); assert!(res.status().is_success());
@ -709,17 +655,16 @@ async fn test_client_streaming_explicit() {
async fn test_body_streaming_implicit() { async fn test_body_streaming_implicit() {
let srv = actix_test::start(|| { let srv = actix_test::start(|| {
App::new().default_service(web::to(|| { App::new().default_service(web::to(|| {
let body = stream::once(async { let body =
Ok::<_, actix_http::Error>(Bytes::from_static(STR.as_bytes())) stream::once(async { Ok::<_, Infallible>(Bytes::from_static(STR.as_bytes())) });
}); HttpResponse::Ok().streaming(body)
HttpResponse::Ok()
.encode_with(ContentEncoding::Gzip)
.streaming(Box::pin(body))
})) }))
}); });
let req = srv.get("/").send(); let req = srv
.get("/")
.insert_header((header::ACCEPT_ENCODING, "gzip"))
.send();
let mut res = req.await.unwrap(); let mut res = req.await.unwrap();
assert!(res.status().is_success()); assert!(res.status().is_success());

View File

@ -22,8 +22,6 @@ pub use crate::service::{HttpServiceFactory, ServiceRequest, ServiceResponse, We
pub use crate::types::{JsonBody, Readlines, UrlEncoded}; pub use crate::types::{JsonBody, Readlines, UrlEncoded};
use crate::{http::header::ContentEncoding, HttpMessage as _};
use actix_router::Patterns; use actix_router::Patterns;
pub(crate) fn ensure_leading_slash(mut patterns: Patterns) -> Patterns { pub(crate) fn ensure_leading_slash(mut patterns: Patterns) -> Patterns {
@ -44,79 +42,3 @@ pub(crate) fn ensure_leading_slash(mut patterns: Patterns) -> Patterns {
patterns patterns
} }
/// Helper trait for managing response encoding.
pub trait BodyEncoding {
/// Get content encoding
fn preferred_encoding(&self) -> Option<ContentEncoding>;
/// Set content encoding to use.
///
/// Must be used with [`Compress`] to take effect.
///
/// [`Compress`]: crate::middleware::Compress
fn encode_with(&mut self, encoding: ContentEncoding) -> &mut Self;
}
struct CompressWith(ContentEncoding);
impl BodyEncoding for crate::HttpResponseBuilder {
fn preferred_encoding(&self) -> Option<ContentEncoding> {
self.extensions().get::<CompressWith>().map(|enc| enc.0)
}
fn encode_with(&mut self, encoding: ContentEncoding) -> &mut Self {
self.extensions_mut().insert(CompressWith(encoding));
self
}
}
impl<B> BodyEncoding for crate::HttpResponse<B> {
fn preferred_encoding(&self) -> Option<ContentEncoding> {
self.extensions().get::<CompressWith>().map(|enc| enc.0)
}
fn encode_with(&mut self, encoding: ContentEncoding) -> &mut Self {
self.extensions_mut().insert(CompressWith(encoding));
self
}
}
impl<B> BodyEncoding for ServiceResponse<B> {
fn preferred_encoding(&self) -> Option<ContentEncoding> {
self.request()
.extensions()
.get::<CompressWith>()
.map(|enc| enc.0)
}
fn encode_with(&mut self, encoding: ContentEncoding) -> &mut Self {
self.request()
.extensions_mut()
.insert(CompressWith(encoding));
self
}
}
// TODO: remove these impls ?
impl BodyEncoding for actix_http::ResponseBuilder {
fn preferred_encoding(&self) -> Option<ContentEncoding> {
self.extensions().get::<CompressWith>().map(|enc| enc.0)
}
fn encode_with(&mut self, encoding: ContentEncoding) -> &mut Self {
self.extensions_mut().insert(CompressWith(encoding));
self
}
}
impl<B> BodyEncoding for actix_http::Response<B> {
fn preferred_encoding(&self) -> Option<ContentEncoding> {
self.extensions().get::<CompressWith>().map(|enc| enc.0)
}
fn encode_with(&mut self, encoding: ContentEncoding) -> &mut Self {
self.extensions_mut().insert(CompressWith(encoding));
self
}
}

View File

@ -17,8 +17,7 @@ fn check_slice_validity(slice: &str) -> bool {
slice.bytes().all(entity_validate_char) slice.bytes().all(entity_validate_char)
} }
/// An entity tag, defined /// An entity tag, defined in [RFC 7232 §2.3].
/// in [RFC 7232 §2.3](https://datatracker.ietf.org/doc/html/rfc7232#section-2.3)
/// ///
/// An entity tag consists of a string enclosed by two literal double quotes. /// An entity tag consists of a string enclosed by two literal double quotes.
/// Preceding the first double quote is an optional weakness indicator, /// Preceding the first double quote is an optional weakness indicator,
@ -48,16 +47,20 @@ fn check_slice_validity(slice: &str) -> bool {
/// | `W/"1"` | `W/"2"` | no match | no match | /// | `W/"1"` | `W/"2"` | no match | no match |
/// | `W/"1"` | `"1"` | no match | match | /// | `W/"1"` | `"1"` | no match | match |
/// | `"1"` | `"1"` | match | match | /// | `"1"` | `"1"` | match | match |
#[derive(Clone, Debug, Eq, PartialEq)] ///
/// [RFC 7232 §2.3](https://datatracker.ietf.org/doc/html/rfc7232#section-2.3)
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EntityTag { pub struct EntityTag {
/// Weakness indicator for the tag /// Weakness indicator for the tag
pub weak: bool, pub weak: bool,
/// The opaque string in between the DQUOTEs /// The opaque string in between the DQUOTEs
tag: String, tag: String,
} }
impl EntityTag { impl EntityTag {
/// Constructs a new EntityTag. /// Constructs a new EntityTag.
///
/// # Panics /// # Panics
/// If the tag contains invalid characters. /// If the tag contains invalid characters.
pub fn new(weak: bool, tag: String) -> EntityTag { pub fn new(weak: bool, tag: String) -> EntityTag {
@ -66,6 +69,7 @@ impl EntityTag {
} }
/// Constructs a new weak EntityTag. /// Constructs a new weak EntityTag.
///
/// # Panics /// # Panics
/// If the tag contains invalid characters. /// If the tag contains invalid characters.
pub fn weak(tag: String) -> EntityTag { pub fn weak(tag: String) -> EntityTag {
@ -73,6 +77,7 @@ impl EntityTag {
} }
/// Constructs a new strong EntityTag. /// Constructs a new strong EntityTag.
///
/// # Panics /// # Panics
/// If the tag contains invalid characters. /// If the tag contains invalid characters.
pub fn strong(tag: String) -> EntityTag { pub fn strong(tag: String) -> EntityTag {
@ -85,6 +90,7 @@ impl EntityTag {
} }
/// Set the tag. /// Set the tag.
///
/// # Panics /// # Panics
/// If the tag contains invalid characters. /// If the tag contains invalid characters.
pub fn set_tag(&mut self, tag: String) { pub fn set_tag(&mut self, tag: String) {

View File

@ -16,7 +16,6 @@ use pin_project_lite::pin_project;
use crate::{ use crate::{
body::{EitherBody, MessageBody}, body::{EitherBody, MessageBody},
dev::BodyEncoding as _,
http::{ http::{
header::{self, AcceptEncoding, Encoding, HeaderValue}, header::{self, AcceptEncoding, Encoding, HeaderValue},
StatusCode, StatusCode,
@ -176,14 +175,10 @@ where
match ready!(this.fut.poll(cx)) { match ready!(this.fut.poll(cx)) {
Ok(resp) => { Ok(resp) => {
let enc = if let Some(enc) = resp.response().preferred_encoding() { let enc = match this.encoding {
enc Encoding::Known(enc) => *enc,
} else { Encoding::Unknown(enc) => {
match this.encoding { unimplemented!("encoding {} should not be here", enc);
Encoding::Known(enc) => *enc,
Encoding::Unknown(enc) => {
unimplemented!("encoding {} should not be here", enc);
}
} }
}; };

View File

@ -1,14 +1,12 @@
use actix_http::ContentEncoding; use actix_http::ContentEncoding;
use actix_web::{ use actix_web::{
dev::BodyEncoding as _,
http::{header, StatusCode}, http::{header, StatusCode},
middleware::Compress, middleware::Compress,
web, App, HttpResponse, web, App, HttpResponse,
}; };
use bytes::Bytes; use bytes::Bytes;
mod test_utils; mod utils;
use test_utils::{brotli, gzip, zstd};
static LOREM: &[u8] = include_bytes!("fixtures/lorem.txt"); static LOREM: &[u8] = include_bytes!("fixtures/lorem.txt");
static LOREM_GZIP: &[u8] = include_bytes!("fixtures/lorem.txt.gz"); static LOREM_GZIP: &[u8] = include_bytes!("fixtures/lorem.txt.gz");
@ -27,7 +25,6 @@ macro_rules! test_server {
web::to(|| { web::to(|| {
HttpResponse::Ok() HttpResponse::Ok()
// signal to compressor that content should not be altered // signal to compressor that content should not be altered
.encode_with(ContentEncoding::Identity)
// signal to client that content is encoded // signal to client that content is encoded
.insert_header(ContentEncoding::Gzip) .insert_header(ContentEncoding::Gzip)
.body(LOREM_GZIP) .body(LOREM_GZIP)
@ -38,7 +35,6 @@ macro_rules! test_server {
web::to(|| { web::to(|| {
HttpResponse::Ok() HttpResponse::Ok()
// signal to compressor that content should not be altered // signal to compressor that content should not be altered
.encode_with(ContentEncoding::Identity)
// signal to client that content is encoded // signal to client that content is encoded
.insert_header(ContentEncoding::Brotli) .insert_header(ContentEncoding::Brotli)
.body(LOREM_BR) .body(LOREM_BR)
@ -49,7 +45,6 @@ macro_rules! test_server {
web::to(|| { web::to(|| {
HttpResponse::Ok() HttpResponse::Ok()
// signal to compressor that content should not be altered // signal to compressor that content should not be altered
.encode_with(ContentEncoding::Identity)
// signal to client that content is encoded // signal to client that content is encoded
.insert_header(ContentEncoding::Zstd) .insert_header(ContentEncoding::Zstd)
.body(LOREM_ZSTD) .body(LOREM_ZSTD)
@ -60,7 +55,7 @@ macro_rules! test_server {
web::to(|| { web::to(|| {
HttpResponse::Ok() HttpResponse::Ok()
// signal to compressor that content should not be altered // signal to compressor that content should not be altered
.encode_with(ContentEncoding::Identity) // .encode_with(ContentEncoding::Identity)
// signal to client that content is encoded as 7zip // signal to client that content is encoded as 7zip
.insert_header((header::CONTENT_ENCODING, "xz")) .insert_header((header::CONTENT_ENCODING, "xz"))
.body(LOREM_XZ) .body(LOREM_XZ)
@ -146,7 +141,7 @@ async fn negotiate_encoding_br() {
.await .await
.unwrap(); .unwrap();
let bytes = res.body().await.unwrap(); let bytes = res.body().await.unwrap();
assert_eq!(brotli::decode(bytes), LOREM); assert_eq!(utils::brotli::decode(bytes), LOREM);
srv.stop().await; srv.stop().await;
} }
@ -175,7 +170,7 @@ async fn negotiate_encoding_zstd() {
.await .await
.unwrap(); .unwrap();
let bytes = res.body().await.unwrap(); let bytes = res.body().await.unwrap();
assert_eq!(zstd::decode(bytes), LOREM); assert_eq!(utils::zstd::decode(bytes), LOREM);
srv.stop().await; srv.stop().await;
} }

View File

@ -12,11 +12,7 @@ use std::{
use actix_web::{ use actix_web::{
cookie::{Cookie, CookieBuilder}, cookie::{Cookie, CookieBuilder},
dev::BodyEncoding, http::{header, StatusCode},
http::{
header::{self, ContentEncoding, ACCEPT_ENCODING, CONTENT_ENCODING, TRANSFER_ENCODING},
StatusCode,
},
middleware::{Compress, NormalizePath, TrailingSlash}, middleware::{Compress, NormalizePath, TrailingSlash},
web, App, Error, HttpResponse, web, App, Error, HttpResponse,
}; };
@ -31,30 +27,11 @@ use openssl::{
x509::X509, x509::X509,
}; };
mod test_utils; mod utils;
use test_utils::{brotli, deflate, gzip, zstd}; use utils::{brotli, deflate, gzip, zstd};
const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ const S: &str = "Hello World ";
Hello World Hello World Hello World Hello World Hello World \ const STR: &str = const_str::repeat!(S, 100);
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World";
#[cfg(feature = "openssl")] #[cfg(feature = "openssl")]
fn openssl_config() -> SslAcceptor { fn openssl_config() -> SslAcceptor {
@ -129,51 +106,52 @@ async fn test_body() {
srv.stop().await; srv.stop().await;
} }
#[actix_rt::test] // enforcing an encoding per-response is removed
async fn test_body_encoding_override() { // #[actix_rt::test]
let srv = actix_test::start_with(actix_test::config().h1(), || { // async fn test_body_encoding_override() {
App::new() // let srv = actix_test::start_with(actix_test::config().h1(), || {
.wrap(Compress::default()) // App::new()
.service(web::resource("/").route(web::to(|| { // .wrap(Compress::default())
HttpResponse::Ok() // .service(web::resource("/").route(web::to(|| {
.encode_with(ContentEncoding::Deflate) // HttpResponse::Ok()
.body(STR) // .encode_with(ContentEncoding::Deflate)
}))) // .body(STR)
.service(web::resource("/raw").route(web::to(|| { // })))
let mut res = HttpResponse::with_body(actix_web::http::StatusCode::OK, STR); // .service(web::resource("/raw").route(web::to(|| {
res.encode_with(ContentEncoding::Deflate); // let mut res = HttpResponse::with_body(actix_web::http::StatusCode::OK, STR);
res.map_into_boxed_body() // res.encode_with(ContentEncoding::Deflate);
}))) // res.map_into_boxed_body()
}); // })))
// });
// Builder // // Builder
let mut res = srv // let mut res = srv
.get("/") // .get("/")
.no_decompress() // .no_decompress()
.append_header((ACCEPT_ENCODING, "deflate")) // .append_header((ACCEPT_ENCODING, "deflate"))
.send() // .send()
.await // .await
.unwrap(); // .unwrap();
assert_eq!(res.status(), StatusCode::OK); // assert_eq!(res.status(), StatusCode::OK);
let bytes = res.body().await.unwrap(); // let bytes = res.body().await.unwrap();
assert_eq!(deflate::decode(bytes), STR.as_bytes()); // assert_eq!(deflate::decode(bytes), STR.as_bytes());
// Raw Response // // Raw Response
let mut res = srv // let mut res = srv
.request(actix_web::http::Method::GET, srv.url("/raw")) // .request(actix_web::http::Method::GET, srv.url("/raw"))
.no_decompress() // .no_decompress()
.append_header((ACCEPT_ENCODING, "deflate")) // .append_header((ACCEPT_ENCODING, "deflate"))
.send() // .send()
.await // .await
.unwrap(); // .unwrap();
assert_eq!(res.status(), StatusCode::OK); // assert_eq!(res.status(), StatusCode::OK);
let bytes = res.body().await.unwrap(); // let bytes = res.body().await.unwrap();
assert_eq!(deflate::decode(bytes), STR.as_bytes()); // assert_eq!(deflate::decode(bytes), STR.as_bytes());
srv.stop().await; // srv.stop().await;
} // }
#[actix_rt::test] #[actix_rt::test]
async fn body_gzip_large() { async fn body_gzip_large() {
@ -191,7 +169,7 @@ async fn body_gzip_large() {
let mut res = srv let mut res = srv
.get("/") .get("/")
.no_decompress() .no_decompress()
.append_header((ACCEPT_ENCODING, "gzip")) .append_header((header::ACCEPT_ENCODING, "gzip"))
.send() .send()
.await .await
.unwrap(); .unwrap();
@ -222,7 +200,7 @@ async fn test_body_gzip_large_random() {
let mut res = srv let mut res = srv
.get("/") .get("/")
.no_decompress() .no_decompress()
.append_header((ACCEPT_ENCODING, "gzip")) .append_header((header::ACCEPT_ENCODING, "gzip"))
.send() .send()
.await .await
.unwrap(); .unwrap();
@ -248,12 +226,15 @@ async fn test_body_chunked_implicit() {
let mut res = srv let mut res = srv
.get("/") .get("/")
.no_decompress() .no_decompress()
.append_header((ACCEPT_ENCODING, "gzip")) .append_header((header::ACCEPT_ENCODING, "gzip"))
.send() .send()
.await .await
.unwrap(); .unwrap();
assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.headers().get(TRANSFER_ENCODING).unwrap(), "chunked"); assert_eq!(
res.headers().get(header::TRANSFER_ENCODING).unwrap(),
"chunked"
);
let bytes = res.body().await.unwrap(); let bytes = res.body().await.unwrap();
assert_eq!(gzip::decode(bytes), STR.as_bytes()); assert_eq!(gzip::decode(bytes), STR.as_bytes());
@ -274,7 +255,7 @@ async fn test_body_br_streaming() {
let mut res = srv let mut res = srv
.get("/") .get("/")
.append_header((ACCEPT_ENCODING, "br")) .append_header((header::ACCEPT_ENCODING, "br"))
.no_decompress() .no_decompress()
.send() .send()
.await .await
@ -319,7 +300,7 @@ async fn test_no_chunking() {
let mut res = srv.get("/").send().await.unwrap(); let mut res = srv.get("/").send().await.unwrap();
assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.status(), StatusCode::OK);
assert!(!res.headers().contains_key(TRANSFER_ENCODING)); assert!(!res.headers().contains_key(header::TRANSFER_ENCODING));
let bytes = res.body().await.unwrap(); let bytes = res.body().await.unwrap();
assert_eq!(bytes, Bytes::from_static(STR.as_ref())); assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
@ -337,7 +318,7 @@ async fn test_body_deflate() {
let mut res = srv let mut res = srv
.get("/") .get("/")
.append_header((ACCEPT_ENCODING, "deflate")) .append_header((header::ACCEPT_ENCODING, "deflate"))
.no_decompress() .no_decompress()
.send() .send()
.await .await
@ -360,7 +341,7 @@ async fn test_body_brotli() {
let mut res = srv let mut res = srv
.get("/") .get("/")
.append_header((ACCEPT_ENCODING, "br")) .append_header((header::ACCEPT_ENCODING, "br"))
.no_decompress() .no_decompress()
.send() .send()
.await .await
@ -383,7 +364,7 @@ async fn test_body_zstd() {
let mut res = srv let mut res = srv
.get("/") .get("/")
.append_header((ACCEPT_ENCODING, "zstd")) .append_header((header::ACCEPT_ENCODING, "zstd"))
.no_decompress() .no_decompress()
.send() .send()
.await .await
@ -409,7 +390,7 @@ async fn test_body_zstd_streaming() {
let mut res = srv let mut res = srv
.get("/") .get("/")
.append_header((ACCEPT_ENCODING, "zstd")) .append_header((header::ACCEPT_ENCODING, "zstd"))
.no_decompress() .no_decompress()
.send() .send()
.await .await
@ -432,7 +413,7 @@ async fn test_zstd_encoding() {
let request = srv let request = srv
.post("/") .post("/")
.append_header((CONTENT_ENCODING, "zstd")) .append_header((header::CONTENT_ENCODING, "zstd"))
.send_body(zstd::encode(STR)); .send_body(zstd::encode(STR));
let mut res = request.await.unwrap(); let mut res = request.await.unwrap();
assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.status(), StatusCode::OK);
@ -463,7 +444,7 @@ async fn test_zstd_encoding_large() {
let request = srv let request = srv
.post("/") .post("/")
.append_header((CONTENT_ENCODING, "zstd")) .append_header((header::CONTENT_ENCODING, "zstd"))
.send_body(zstd::encode(&data)); .send_body(zstd::encode(&data));
let mut res = request.await.unwrap(); let mut res = request.await.unwrap();
assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.status(), StatusCode::OK);
@ -484,7 +465,7 @@ async fn test_encoding() {
let request = srv let request = srv
.post("/") .post("/")
.insert_header((CONTENT_ENCODING, "gzip")) .insert_header((header::CONTENT_ENCODING, "gzip"))
.send_body(gzip::encode(STR)); .send_body(gzip::encode(STR));
let mut res = request.await.unwrap(); let mut res = request.await.unwrap();
assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.status(), StatusCode::OK);
@ -505,7 +486,7 @@ async fn test_gzip_encoding() {
let request = srv let request = srv
.post("/") .post("/")
.append_header((CONTENT_ENCODING, "gzip")) .append_header((header::CONTENT_ENCODING, "gzip"))
.send_body(gzip::encode(STR)); .send_body(gzip::encode(STR));
let mut res = request.await.unwrap(); let mut res = request.await.unwrap();
assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.status(), StatusCode::OK);
@ -527,7 +508,7 @@ async fn test_gzip_encoding_large() {
let req = srv let req = srv
.post("/") .post("/")
.append_header((CONTENT_ENCODING, "gzip")) .append_header((header::CONTENT_ENCODING, "gzip"))
.send_body(gzip::encode(&data)); .send_body(gzip::encode(&data));
let mut res = req.await.unwrap(); let mut res = req.await.unwrap();
assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.status(), StatusCode::OK);
@ -554,7 +535,7 @@ async fn test_reading_gzip_encoding_large_random() {
let request = srv let request = srv
.post("/") .post("/")
.append_header((CONTENT_ENCODING, "gzip")) .append_header((header::CONTENT_ENCODING, "gzip"))
.send_body(gzip::encode(&data)); .send_body(gzip::encode(&data));
let mut res = request.await.unwrap(); let mut res = request.await.unwrap();
assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.status(), StatusCode::OK);
@ -575,7 +556,7 @@ async fn test_reading_deflate_encoding() {
let request = srv let request = srv
.post("/") .post("/")
.append_header((CONTENT_ENCODING, "deflate")) .append_header((header::CONTENT_ENCODING, "deflate"))
.send_body(deflate::encode(STR)); .send_body(deflate::encode(STR));
let mut res = request.await.unwrap(); let mut res = request.await.unwrap();
assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.status(), StatusCode::OK);
@ -597,7 +578,7 @@ async fn test_reading_deflate_encoding_large() {
let request = srv let request = srv
.post("/") .post("/")
.append_header((CONTENT_ENCODING, "deflate")) .append_header((header::CONTENT_ENCODING, "deflate"))
.send_body(deflate::encode(&data)); .send_body(deflate::encode(&data));
let mut res = request.await.unwrap(); let mut res = request.await.unwrap();
assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.status(), StatusCode::OK);
@ -624,7 +605,7 @@ async fn test_reading_deflate_encoding_large_random() {
let request = srv let request = srv
.post("/") .post("/")
.append_header((CONTENT_ENCODING, "deflate")) .append_header((header::CONTENT_ENCODING, "deflate"))
.send_body(deflate::encode(&data)); .send_body(deflate::encode(&data));
let mut res = request.await.unwrap(); let mut res = request.await.unwrap();
assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.status(), StatusCode::OK);
@ -646,7 +627,7 @@ async fn test_brotli_encoding() {
let request = srv let request = srv
.post("/") .post("/")
.append_header((CONTENT_ENCODING, "br")) .append_header((header::CONTENT_ENCODING, "br"))
.send_body(brotli::encode(STR)); .send_body(brotli::encode(STR));
let mut res = request.await.unwrap(); let mut res = request.await.unwrap();
assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.status(), StatusCode::OK);
@ -677,7 +658,7 @@ async fn test_brotli_encoding_large() {
let request = srv let request = srv
.post("/") .post("/")
.append_header((CONTENT_ENCODING, "br")) .append_header((header::CONTENT_ENCODING, "br"))
.send_body(brotli::encode(&data)); .send_body(brotli::encode(&data));
let mut res = request.await.unwrap(); let mut res = request.await.unwrap();
assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.status(), StatusCode::OK);
@ -697,8 +678,9 @@ async fn test_brotli_encoding_large_openssl() {
let srv = let srv =
actix_test::start_with(actix_test::config().openssl(openssl_config()), move || { actix_test::start_with(actix_test::config().openssl(openssl_config()), move || {
App::new().service(web::resource("/").route(web::to(|bytes: Bytes| { App::new().service(web::resource("/").route(web::to(|bytes: Bytes| {
// echo decompressed request body back in response
HttpResponse::Ok() HttpResponse::Ok()
.encode_with(ContentEncoding::Identity) .insert_header(header::ContentEncoding::Identity)
.body(bytes) .body(bytes)
}))) })))
}); });
@ -758,15 +740,16 @@ mod plus_rustls {
let srv = actix_test::start_with(actix_test::config().rustls(tls_config()), || { let srv = actix_test::start_with(actix_test::config().rustls(tls_config()), || {
App::new().service(web::resource("/").route(web::to(|bytes: Bytes| { App::new().service(web::resource("/").route(web::to(|bytes: Bytes| {
// echo decompressed request body back in response
HttpResponse::Ok() HttpResponse::Ok()
.encode_with(ContentEncoding::Identity) .insert_header(header::ContentEncoding::Identity)
.body(bytes) .body(bytes)
}))) })))
}); });
let req = srv let req = srv
.post("/") .post("/")
.insert_header((actix_web::http::header::CONTENT_ENCODING, "deflate")) .insert_header((header::CONTENT_ENCODING, "deflate"))
.send_stream(TestBody::new(Bytes::from(deflate::encode(&data)), 1024)); .send_stream(TestBody::new(Bytes::from(deflate::encode(&data)), 1024));
let mut res = req.await.unwrap(); let mut res = req.await.unwrap();
@ -931,14 +914,14 @@ async fn test_accept_encoding_no_match() {
let mut res = srv let mut res = srv
.get("/") .get("/")
.insert_header((ACCEPT_ENCODING, "xz, identity;q=0")) .insert_header((header::ACCEPT_ENCODING, "xz, identity;q=0"))
.no_decompress() .no_decompress()
.send() .send()
.await .await
.unwrap(); .unwrap();
assert_eq!(res.status(), StatusCode::NOT_ACCEPTABLE); assert_eq!(res.status(), StatusCode::NOT_ACCEPTABLE);
assert_eq!(res.headers().get(CONTENT_ENCODING), None); assert_eq!(res.headers().get(header::CONTENT_ENCODING), None);
let bytes = res.body().await.unwrap(); let bytes = res.body().await.unwrap();
// body should contain the supported encodings // body should contain the supported encodings

76
tests/utils.rs Normal file
View File

@ -0,0 +1,76 @@
// compiling some tests will trigger unused function warnings even though other tests use them
#![allow(dead_code)]
use std::io::{Read as _, Write as _};
pub mod gzip {
use super::*;
use flate2::{read::GzDecoder, write::GzEncoder, Compression};
pub fn encode(bytes: impl AsRef<[u8]>) -> Vec<u8> {
let mut encoder = GzEncoder::new(Vec::new(), Compression::fast());
encoder.write_all(bytes.as_ref()).unwrap();
encoder.finish().unwrap()
}
pub fn decode(bytes: impl AsRef<[u8]>) -> Vec<u8> {
let mut decoder = GzDecoder::new(bytes.as_ref());
let mut buf = Vec::new();
decoder.read_to_end(&mut buf).unwrap();
buf
}
}
pub mod deflate {
use super::*;
use flate2::{read::ZlibDecoder, write::ZlibEncoder, Compression};
pub fn encode(bytes: impl AsRef<[u8]>) -> Vec<u8> {
let mut encoder = ZlibEncoder::new(Vec::new(), Compression::fast());
encoder.write_all(bytes.as_ref()).unwrap();
encoder.finish().unwrap()
}
pub fn decode(bytes: impl AsRef<[u8]>) -> Vec<u8> {
let mut decoder = ZlibDecoder::new(bytes.as_ref());
let mut buf = Vec::new();
decoder.read_to_end(&mut buf).unwrap();
buf
}
}
pub mod brotli {
use super::*;
use ::brotli2::{read::BrotliDecoder, write::BrotliEncoder};
pub fn encode(bytes: impl AsRef<[u8]>) -> Vec<u8> {
let mut encoder = BrotliEncoder::new(Vec::new(), 3);
encoder.write_all(bytes.as_ref()).unwrap();
encoder.finish().unwrap()
}
pub fn decode(bytes: impl AsRef<[u8]>) -> Vec<u8> {
let mut decoder = BrotliDecoder::new(bytes.as_ref());
let mut buf = Vec::new();
decoder.read_to_end(&mut buf).unwrap();
buf
}
}
pub mod zstd {
use super::*;
use ::zstd::stream::{read::Decoder, write::Encoder};
pub fn encode(bytes: impl AsRef<[u8]>) -> Vec<u8> {
let mut encoder = Encoder::new(Vec::new(), 3).unwrap();
encoder.write_all(bytes.as_ref()).unwrap();
encoder.finish().unwrap()
}
pub fn decode(bytes: impl AsRef<[u8]>) -> Vec<u8> {
let mut decoder = Decoder::new(bytes.as_ref()).unwrap();
let mut buf = Vec::new();
decoder.read_to_end(&mut buf).unwrap();
buf
}
}