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();
let res = test::call_service(&srv, request).await;
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]

View File

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

View File

@ -6,7 +6,7 @@ use ahash::AHashMap;
use http::header::{HeaderName, HeaderValue};
use smallvec::{smallvec, SmallVec};
use crate::header::AsHeaderName;
use super::AsHeaderName;
/// 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.
pub trait Header: TryIntoHeaderValue {
/// Returns the name of the header field
/// Returns the name of the header field.
fn name() -> HeaderName;
/// Parse a header
/// Parse the header from a HTTP message.
fn parse<M: HttpMessage>(msg: &M) -> Result<Self, ParseError>;
}

View File

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

View File

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

View File

@ -1,5 +1,6 @@
use std::{
collections::HashMap,
convert::Infallible,
io::{Read, Write},
net::{IpAddr, Ipv4Addr},
sync::{
@ -15,43 +16,16 @@ use cookie::Cookie;
use futures_util::stream;
use rand::Rng;
#[cfg(feature = "compress-brotli")]
use brotli2::write::BrotliEncoder;
#[cfg(feature = "compress-gzip")]
use flate2::{read::GzDecoder, write::GzEncoder, Compression};
use actix_http::{ContentEncoding, HttpService, StatusCode};
use actix_http::{HttpService, StatusCode};
use actix_http_test::test_server;
use actix_service::{fn_service, map_config, ServiceFactoryExt as _};
use actix_web::{
dev::{AppConfig, BodyEncoding},
http::header,
web, App, Error, HttpRequest, HttpResponse,
};
use actix_web::{dev::AppConfig, http::header, web, App, Error, HttpRequest, HttpResponse};
use awc::error::{JsonPayloadError, PayloadError, SendRequestError};
const STR: &str = "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 \
Hello World Hello World Hello World Hello World Hello World \
Hello World Hello World Hello World Hello World Hello World";
mod utils;
const S: &str = "Hello World ";
const STR: &str = const_str::repeat!(S, 100);
#[actix_rt::test]
async fn test_simple() {
@ -471,15 +445,12 @@ async fn test_no_decompress() {
let srv = actix_test::start(|| {
App::new()
.wrap(actix_web::middleware::Compress::default())
.service(web::resource("/").route(web::to(|| {
let mut res = HttpResponse::Ok().body(STR);
res.encode_with(header::ContentEncoding::Gzip);
res
})))
.service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))))
});
let mut res = awc::Client::new()
.get(srv.url("/"))
.insert_header((header::ACCEPT_ENCODING, "gzip"))
.no_decompress()
.send()
.await
@ -488,15 +459,12 @@ async fn test_no_decompress() {
// read response
let bytes = res.body().await.unwrap();
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()));
assert_eq!(utils::gzip::decode(bytes), STR.as_bytes());
// POST
let mut res = awc::Client::new()
.post(srv.url("/"))
.insert_header((header::ACCEPT_ENCODING, "gzip"))
.no_decompress()
.send()
.await
@ -504,10 +472,7 @@ async fn test_no_decompress() {
assert!(res.status().is_success());
let bytes = res.body().await.unwrap();
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()));
assert_eq!(utils::gzip::decode(bytes), STR.as_bytes());
}
#[cfg(feature = "compress-gzip")]
@ -515,13 +480,9 @@ async fn test_no_decompress() {
async fn test_client_gzip_encoding() {
let srv = actix_test::start(|| {
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()
.insert_header(("content-encoding", "gzip"))
.body(data)
.insert_header(header::ContentEncoding::Gzip)
.body(utils::gzip::encode(STR))
})))
});
@ -531,7 +492,7 @@ async fn test_client_gzip_encoding() {
// read response
let bytes = response.body().await.unwrap();
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
assert_eq!(bytes, STR);
}
#[cfg(feature = "compress-gzip")]
@ -539,13 +500,9 @@ async fn test_client_gzip_encoding() {
async fn test_client_gzip_encoding_large() {
let srv = actix_test::start(|| {
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()
.insert_header(("content-encoding", "gzip"))
.body(data)
.insert_header(header::ContentEncoding::Gzip)
.body(utils::gzip::encode(STR.repeat(10)))
})))
});
@ -555,7 +512,7 @@ async fn test_client_gzip_encoding_large() {
// read response
let bytes = response.body().await.unwrap();
assert_eq!(bytes, Bytes::from(STR.repeat(10)));
assert_eq!(bytes, STR.repeat(10));
}
#[cfg(feature = "compress-gzip")]
@ -569,12 +526,9 @@ async fn test_client_gzip_encoding_large_random() {
let srv = actix_test::start(|| {
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()
.insert_header(("content-encoding", "gzip"))
.body(data)
.insert_header(header::ContentEncoding::Gzip)
.body(utils::gzip::encode(data))
})))
});
@ -584,7 +538,7 @@ async fn test_client_gzip_encoding_large_random() {
// read response
let bytes = response.body().await.unwrap();
assert_eq!(bytes, Bytes::from(data));
assert_eq!(bytes, data);
}
#[cfg(feature = "compress-brotli")]
@ -592,12 +546,9 @@ async fn test_client_gzip_encoding_large_random() {
async fn test_client_brotli_encoding() {
let srv = actix_test::start(|| {
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()
.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(|| {
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()
.insert_header(("content-encoding", "br"))
.body(data)
.insert_header(header::ContentEncoding::Brotli)
.body(utils::brotli::encode(&data))
})))
});
@ -636,27 +584,25 @@ async fn test_client_brotli_encoding_large_random() {
// read response
let bytes = response.body().await.unwrap();
assert_eq!(bytes.len(), data.len());
assert_eq!(bytes, Bytes::from(data));
assert_eq!(bytes, data);
}
#[actix_rt::test]
async fn test_client_deflate_encoding() {
let srv = actix_test::start(|| {
App::new().default_service(web::to(|body: Bytes| {
HttpResponse::Ok()
.encode_with(ContentEncoding::Brotli)
.body(body)
}))
App::new().default_service(web::to(|body: Bytes| HttpResponse::Ok().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();
assert_eq!(res.status(), StatusCode::OK);
let bytes = res.body().await.unwrap();
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
assert_eq!(bytes, STR);
}
#[actix_rt::test]
@ -668,14 +614,13 @@ async fn test_client_deflate_encoding_large_random() {
.collect::<String>();
let srv = actix_test::start(|| {
App::new().default_service(web::to(|body: Bytes| {
HttpResponse::Ok()
.encode_with(ContentEncoding::Brotli)
.body(body)
}))
App::new().default_service(web::to(|body: Bytes| HttpResponse::Ok().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 bytes = res.body().await.unwrap();
@ -688,15 +633,16 @@ async fn test_client_deflate_encoding_large_random() {
async fn test_client_streaming_explicit() {
let srv = actix_test::start(|| {
App::new().default_service(web::to(|body: web::Payload| {
HttpResponse::Ok()
.encode_with(ContentEncoding::Identity)
.streaming(body)
HttpResponse::Ok().streaming(body)
}))
});
let body =
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();
assert!(res.status().is_success());
@ -709,17 +655,16 @@ async fn test_client_streaming_explicit() {
async fn test_body_streaming_implicit() {
let srv = actix_test::start(|| {
App::new().default_service(web::to(|| {
let body = stream::once(async {
Ok::<_, actix_http::Error>(Bytes::from_static(STR.as_bytes()))
});
HttpResponse::Ok()
.encode_with(ContentEncoding::Gzip)
.streaming(Box::pin(body))
let body =
stream::once(async { Ok::<_, Infallible>(Bytes::from_static(STR.as_bytes())) });
HttpResponse::Ok().streaming(body)
}))
});
let req = srv.get("/").send();
let req = srv
.get("/")
.insert_header((header::ACCEPT_ENCODING, "gzip"))
.send();
let mut res = req.await.unwrap();
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};
use crate::{http::header::ContentEncoding, HttpMessage as _};
use actix_router::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
}
/// 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)
}
/// An entity tag, defined
/// in [RFC 7232 §2.3](https://datatracker.ietf.org/doc/html/rfc7232#section-2.3)
/// An entity tag, defined in [RFC 7232 §2.3].
///
/// An entity tag consists of a string enclosed by two literal double quotes.
/// 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"` | `"1"` | no 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 {
/// Weakness indicator for the tag
pub weak: bool,
/// The opaque string in between the DQUOTEs
tag: String,
}
impl EntityTag {
/// Constructs a new EntityTag.
///
/// # Panics
/// If the tag contains invalid characters.
pub fn new(weak: bool, tag: String) -> EntityTag {
@ -66,6 +69,7 @@ impl EntityTag {
}
/// Constructs a new weak EntityTag.
///
/// # Panics
/// If the tag contains invalid characters.
pub fn weak(tag: String) -> EntityTag {
@ -73,6 +77,7 @@ impl EntityTag {
}
/// Constructs a new strong EntityTag.
///
/// # Panics
/// If the tag contains invalid characters.
pub fn strong(tag: String) -> EntityTag {
@ -85,6 +90,7 @@ impl EntityTag {
}
/// Set the tag.
///
/// # Panics
/// If the tag contains invalid characters.
pub fn set_tag(&mut self, tag: String) {

View File

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

View File

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

View File

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