1
0
Fork 0
mirror of https://github.com/actix/actix-web.git synced 2024-06-10 17:29:36 +00:00

remove BodyEncoding trait (#2565)

This commit is contained in:
Rob Ede 2022-01-03 18:46:04 +00:00 committed by GitHub
parent 19a46e3925
commit 0bc4ae9158
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 355 additions and 403 deletions

View file

@ -14,6 +14,7 @@ jobs:
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
with: with:
toolchain: stable toolchain: stable
profile: minimal
components: rustfmt components: rustfmt
- name: Check with rustfmt - name: Check with rustfmt
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
@ -30,10 +31,18 @@ jobs:
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
with: with:
toolchain: stable toolchain: stable
profile: minimal
components: clippy components: clippy
override: true override: true
- name: Generate Cargo.lock
uses: actions-rs/cargo@v1
with: { command: generate-lockfile }
- name: Cache Dependencies
uses: Swatinem/rust-cache@v1.2.0
- name: Check with Clippy - name: Check with Clippy
uses: actions-rs/clippy-check@v1 uses: actions-rs/clippy-check@v1
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
args: --workspace --all-features --tests args: --workspace --tests --examples --all-features

View file

@ -15,8 +15,10 @@
### Removed ### Removed
- `Compress::new`; restricting compression algorithm is done through feature flags. [#2501] - `Compress::new`; restricting compression algorithm is done through feature flags. [#2501]
- `BodyEncoding` trait; signalling content encoding is now only done via the `Content-Encoding` header. [#2565]
[#2501]: https://github.com/actix/actix-web/pull/2501 [#2501]: https://github.com/actix/actix-web/pull/2501
[#2565]: https://github.com/actix/actix-web/pull/2565
## 4.0.0-beta.18 - 2021-12-29 ## 4.0.0-beta.18 - 2021-12-29

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 = {
@ -386,7 +381,7 @@ impl NamedFile {
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)
.expect("modification time must be after epoch"); .expect("modification time must be after epoch");
header::EntityTag::strong(format!( header::EntityTag::new_strong(format!(
"{:x}:{:x}:{:x}:{:x}", "{:x}:{:x}:{:x}:{:x}",
ino, ino,
self.md.len(), self.md.len(),
@ -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

@ -13,6 +13,7 @@
- `QualityItem::min` semantics changed with `QualityItem::MIN`. [#2501] - `QualityItem::min` semantics changed with `QualityItem::MIN`. [#2501]
- Rename `ContentEncoding::{Br => Brotli}`. [#2501] - Rename `ContentEncoding::{Br => Brotli}`. [#2501]
- Minimum supported Rust version (MSRV) is now 1.54. - Minimum supported Rust version (MSRV) is now 1.54.
- Rename `header::EntityTag::{weak => new_weak, strong => new_strong}`. [#2565]
### Fixed ### Fixed
- `ContentEncoding::Identity` can now be parsed from a string. [#2501] - `ContentEncoding::Identity` can now be parsed from a string. [#2501]
@ -23,6 +24,7 @@
- `ContentEncoding::is_compression()`. [#2501] - `ContentEncoding::is_compression()`. [#2501]
[#2501]: https://github.com/actix/actix-web/pull/2501 [#2501]: https://github.com/actix/actix-web/pull/2501
[#2565]: https://github.com/actix/actix-web/pull/2565
## 3.0.0-beta.17 - 2021-12-27 ## 3.0.0-beta.17 - 2021-12-27

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,51 +69,64 @@ 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 new_weak(tag: String) -> EntityTag {
EntityTag::new(true, tag) EntityTag::new(true, tag)
} }
#[deprecated(since = "3.0.0", note = "Renamed to `new_weak`.")]
pub fn weak(tag: String) -> EntityTag {
Self::new_weak(tag)
}
/// 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 new_strong(tag: String) -> EntityTag {
EntityTag::new(false, tag) EntityTag::new(false, tag)
} }
/// Get the tag. #[deprecated(since = "3.0.0", note = "Renamed to `new_strong`.")]
pub fn strong(tag: String) -> EntityTag {
Self::new_strong(tag)
}
/// Returns tag.
pub fn tag(&self) -> &str { pub fn tag(&self) -> &str {
self.tag.as_ref() self.tag.as_ref()
} }
/// Set the tag. /// Sets 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: impl Into<String>) {
let tag = tag.into();
assert!(check_slice_validity(&tag), "Invalid tag: {:?}", tag); assert!(check_slice_validity(&tag), "Invalid tag: {:?}", tag);
self.tag = tag self.tag = tag
} }
/// For strong comparison two entity-tags are equivalent if both are not /// For strong comparison two entity-tags are equivalent if both are not weak and their
/// weak and their opaque-tags match character-by-character. /// opaque-tags match character-by-character.
pub fn strong_eq(&self, other: &EntityTag) -> bool { pub fn strong_eq(&self, other: &EntityTag) -> bool {
!self.weak && !other.weak && self.tag == other.tag !self.weak && !other.weak && self.tag == other.tag
} }
/// For weak comparison two entity-tags are equivalent if their /// For weak comparison two entity-tags are equivalent if their opaque-tags match
/// opaque-tags match character-by-character, regardless of either or /// character-by-character, regardless of either or both being tagged as "weak".
/// both being tagged as "weak".
pub fn weak_eq(&self, other: &EntityTag) -> bool { pub fn weak_eq(&self, other: &EntityTag) -> bool {
self.tag == other.tag self.tag == other.tag
} }
/// The inverse of `EntityTag.strong_eq()`. /// Returns the inverse of `strong_eq()`.
pub fn strong_ne(&self, other: &EntityTag) -> bool { pub fn strong_ne(&self, other: &EntityTag) -> bool {
!self.strong_eq(other) !self.strong_eq(other)
} }
/// The inverse of `EntityTag.weak_eq()`. /// Returns inverse of `weak_eq()`.
pub fn weak_ne(&self, other: &EntityTag) -> bool { pub fn weak_ne(&self, other: &EntityTag) -> bool {
!self.weak_eq(other) !self.weak_eq(other)
} }
@ -178,23 +194,23 @@ mod tests {
// Expected success // Expected success
assert_eq!( assert_eq!(
"\"foobar\"".parse::<EntityTag>().unwrap(), "\"foobar\"".parse::<EntityTag>().unwrap(),
EntityTag::strong("foobar".to_owned()) EntityTag::new_strong("foobar".to_owned())
); );
assert_eq!( assert_eq!(
"\"\"".parse::<EntityTag>().unwrap(), "\"\"".parse::<EntityTag>().unwrap(),
EntityTag::strong("".to_owned()) EntityTag::new_strong("".to_owned())
); );
assert_eq!( assert_eq!(
"W/\"weaktag\"".parse::<EntityTag>().unwrap(), "W/\"weaktag\"".parse::<EntityTag>().unwrap(),
EntityTag::weak("weaktag".to_owned()) EntityTag::new_weak("weaktag".to_owned())
); );
assert_eq!( assert_eq!(
"W/\"\x65\x62\"".parse::<EntityTag>().unwrap(), "W/\"\x65\x62\"".parse::<EntityTag>().unwrap(),
EntityTag::weak("\x65\x62".to_owned()) EntityTag::new_weak("\x65\x62".to_owned())
); );
assert_eq!( assert_eq!(
"W/\"\"".parse::<EntityTag>().unwrap(), "W/\"\"".parse::<EntityTag>().unwrap(),
EntityTag::weak("".to_owned()) EntityTag::new_weak("".to_owned())
); );
} }
@ -214,19 +230,19 @@ mod tests {
#[test] #[test]
fn test_etag_fmt() { fn test_etag_fmt() {
assert_eq!( assert_eq!(
format!("{}", EntityTag::strong("foobar".to_owned())), format!("{}", EntityTag::new_strong("foobar".to_owned())),
"\"foobar\"" "\"foobar\""
); );
assert_eq!(format!("{}", EntityTag::strong("".to_owned())), "\"\""); assert_eq!(format!("{}", EntityTag::new_strong("".to_owned())), "\"\"");
assert_eq!( assert_eq!(
format!("{}", EntityTag::weak("weak-etag".to_owned())), format!("{}", EntityTag::new_weak("weak-etag".to_owned())),
"W/\"weak-etag\"" "W/\"weak-etag\""
); );
assert_eq!( assert_eq!(
format!("{}", EntityTag::weak("\u{0065}".to_owned())), format!("{}", EntityTag::new_weak("\u{0065}".to_owned())),
"W/\"\x65\"" "W/\"\x65\""
); );
assert_eq!(format!("{}", EntityTag::weak("".to_owned())), "W/\"\""); assert_eq!(format!("{}", EntityTag::new_weak("".to_owned())), "W/\"\"");
} }
#[test] #[test]
@ -237,29 +253,29 @@ mod tests {
// | `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 |
let mut etag1 = EntityTag::weak("1".to_owned()); let mut etag1 = EntityTag::new_weak("1".to_owned());
let mut etag2 = EntityTag::weak("1".to_owned()); let mut etag2 = EntityTag::new_weak("1".to_owned());
assert!(!etag1.strong_eq(&etag2)); assert!(!etag1.strong_eq(&etag2));
assert!(etag1.weak_eq(&etag2)); assert!(etag1.weak_eq(&etag2));
assert!(etag1.strong_ne(&etag2)); assert!(etag1.strong_ne(&etag2));
assert!(!etag1.weak_ne(&etag2)); assert!(!etag1.weak_ne(&etag2));
etag1 = EntityTag::weak("1".to_owned()); etag1 = EntityTag::new_weak("1".to_owned());
etag2 = EntityTag::weak("2".to_owned()); etag2 = EntityTag::new_weak("2".to_owned());
assert!(!etag1.strong_eq(&etag2)); assert!(!etag1.strong_eq(&etag2));
assert!(!etag1.weak_eq(&etag2)); assert!(!etag1.weak_eq(&etag2));
assert!(etag1.strong_ne(&etag2)); assert!(etag1.strong_ne(&etag2));
assert!(etag1.weak_ne(&etag2)); assert!(etag1.weak_ne(&etag2));
etag1 = EntityTag::weak("1".to_owned()); etag1 = EntityTag::new_weak("1".to_owned());
etag2 = EntityTag::strong("1".to_owned()); etag2 = EntityTag::new_strong("1".to_owned());
assert!(!etag1.strong_eq(&etag2)); assert!(!etag1.strong_eq(&etag2));
assert!(etag1.weak_eq(&etag2)); assert!(etag1.weak_eq(&etag2));
assert!(etag1.strong_ne(&etag2)); assert!(etag1.strong_ne(&etag2));
assert!(!etag1.weak_ne(&etag2)); assert!(!etag1.weak_ne(&etag2));
etag1 = EntityTag::strong("1".to_owned()); etag1 = EntityTag::new_strong("1".to_owned());
etag2 = EntityTag::strong("1".to_owned()); etag2 = EntityTag::new_strong("1".to_owned());
assert!(etag1.strong_eq(&etag2)); assert!(etag1.strong_eq(&etag2));
assert!(etag1.weak_eq(&etag2)); assert!(etag1.weak_eq(&etag2));
assert!(!etag1.strong_ne(&etag2)); assert!(!etag1.strong_ne(&etag2));

View file

@ -31,7 +31,7 @@ crate::http::header::common_header! {
/// ///
/// let mut builder = HttpResponse::Ok(); /// let mut builder = HttpResponse::Ok();
/// builder.insert_header( /// builder.insert_header(
/// ETag(EntityTag::new(false, "xyzzy".to_owned())) /// ETag(EntityTag::new_strong("xyzzy".to_owned()))
/// ); /// );
/// ``` /// ```
/// ///
@ -41,7 +41,7 @@ crate::http::header::common_header! {
/// ///
/// let mut builder = HttpResponse::Ok(); /// let mut builder = HttpResponse::Ok();
/// builder.insert_header( /// builder.insert_header(
/// ETag(EntityTag::new(true, "xyzzy".to_owned())) /// ETag(EntityTag::new_weak("xyzzy".to_owned()))
/// ); /// );
/// ``` /// ```
(ETag, ETAG) => [EntityTag] (ETag, ETAG) => [EntityTag]
@ -50,29 +50,29 @@ crate::http::header::common_header! {
// From the RFC // From the RFC
crate::http::header::common_header_test!(test1, crate::http::header::common_header_test!(test1,
vec![b"\"xyzzy\""], vec![b"\"xyzzy\""],
Some(ETag(EntityTag::new(false, "xyzzy".to_owned())))); Some(ETag(EntityTag::new_strong("xyzzy".to_owned()))));
crate::http::header::common_header_test!(test2, crate::http::header::common_header_test!(test2,
vec![b"W/\"xyzzy\""], vec![b"W/\"xyzzy\""],
Some(ETag(EntityTag::new(true, "xyzzy".to_owned())))); Some(ETag(EntityTag::new_weak("xyzzy".to_owned()))));
crate::http::header::common_header_test!(test3, crate::http::header::common_header_test!(test3,
vec![b"\"\""], vec![b"\"\""],
Some(ETag(EntityTag::new(false, "".to_owned())))); Some(ETag(EntityTag::new_strong("".to_owned()))));
// Own tests // Own tests
crate::http::header::common_header_test!(test4, crate::http::header::common_header_test!(test4,
vec![b"\"foobar\""], vec![b"\"foobar\""],
Some(ETag(EntityTag::new(false, "foobar".to_owned())))); Some(ETag(EntityTag::new_strong("foobar".to_owned()))));
crate::http::header::common_header_test!(test5, crate::http::header::common_header_test!(test5,
vec![b"\"\""], vec![b"\"\""],
Some(ETag(EntityTag::new(false, "".to_owned())))); Some(ETag(EntityTag::new_strong("".to_owned()))));
crate::http::header::common_header_test!(test6, crate::http::header::common_header_test!(test6,
vec![b"W/\"weak-etag\""], vec![b"W/\"weak-etag\""],
Some(ETag(EntityTag::new(true, "weak-etag".to_owned())))); Some(ETag(EntityTag::new_weak("weak-etag".to_owned()))));
crate::http::header::common_header_test!(test7, crate::http::header::common_header_test!(test7,
vec![b"W/\"\x65\x62\""], vec![b"W/\"\x65\x62\""],
Some(ETag(EntityTag::new(true, "\u{0065}\u{0062}".to_owned())))); Some(ETag(EntityTag::new_weak("\u{0065}\u{0062}".to_owned()))));
crate::http::header::common_header_test!(test8, crate::http::header::common_header_test!(test8,
vec![b"W/\"\""], vec![b"W/\"\""],
Some(ETag(EntityTag::new(true, "".to_owned())))); Some(ETag(EntityTag::new_weak("".to_owned()))));
crate::http::header::common_header_test!(test9, crate::http::header::common_header_test!(test9,
vec![b"no-dquotes"], vec![b"no-dquotes"],
None::<ETag>); None::<ETag>);

View file

@ -54,14 +54,15 @@ common_header! {
test1, test1,
vec![b"\"xyzzy\""], vec![b"\"xyzzy\""],
Some(HeaderField::Items( Some(HeaderField::Items(
vec![EntityTag::new(false, "xyzzy".to_owned())]))); vec![EntityTag::new_strong("xyzzy".to_owned())])));
crate::http::header::common_header_test!( crate::http::header::common_header_test!(
test2, test2,
vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""], vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""],
Some(HeaderField::Items( Some(HeaderField::Items(
vec![EntityTag::new(false, "xyzzy".to_owned()), vec![EntityTag::new_strong("xyzzy".to_owned()),
EntityTag::new(false, "r2d2xxxx".to_owned()), EntityTag::new_strong("r2d2xxxx".to_owned()),
EntityTag::new(false, "c3piozzzz".to_owned())]))); EntityTag::new_strong("c3piozzzz".to_owned())])));
crate::http::header::common_header_test!(test3, vec![b"*"], Some(IfMatch::Any)); crate::http::header::common_header_test!(test3, vec![b"*"], Some(IfMatch::Any));
} }
} }

View file

@ -82,8 +82,8 @@ mod tests {
if_none_match = Header::parse(&req); if_none_match = Header::parse(&req);
let mut entities: Vec<EntityTag> = Vec::new(); let mut entities: Vec<EntityTag> = Vec::new();
let foobar_etag = EntityTag::new(false, "foobar".to_owned()); let foobar_etag = EntityTag::new_strong("foobar".to_owned());
let weak_etag = EntityTag::new(true, "weak-etag".to_owned()); let weak_etag = EntityTag::new_weak("weak-etag".to_owned());
entities.push(foobar_etag); entities.push(foobar_etag);
entities.push(weak_etag); entities.push(weak_etag);
assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Items(entities))); assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Items(entities)));

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,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 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)
@ -117,7 +111,7 @@ async fn negotiate_encoding_gzip() {
.await .await
.unwrap(); .unwrap();
let bytes = res.body().await.unwrap(); let bytes = res.body().await.unwrap();
assert_eq!(gzip::decode(bytes), LOREM); assert_eq!(utils::gzip::decode(bytes), LOREM);
srv.stop().await; srv.stop().await;
} }
@ -146,7 +140,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 +169,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,10 @@ use openssl::{
x509::X509, x509::X509,
}; };
mod test_utils; mod utils;
use test_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 +105,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!(utils::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!(utils::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,14 +168,14 @@ 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();
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!(gzip::decode(bytes), data.as_bytes()); assert_eq!(utils::gzip::decode(bytes), data.as_bytes());
srv.stop().await; srv.stop().await;
} }
@ -222,14 +199,14 @@ 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();
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!(gzip::decode(bytes), data.as_bytes()); assert_eq!(utils::gzip::decode(bytes), data.as_bytes());
srv.stop().await; srv.stop().await;
} }
@ -248,15 +225,18 @@ 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!(utils::gzip::decode(bytes), STR.as_bytes());
srv.stop().await; srv.stop().await;
} }
@ -274,7 +254,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
@ -282,7 +262,7 @@ async fn test_body_br_streaming() {
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!(brotli::decode(bytes), STR.as_bytes()); assert_eq!(utils::brotli::decode(bytes), STR.as_bytes());
srv.stop().await; srv.stop().await;
} }
@ -319,7 +299,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 +317,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
@ -345,7 +325,7 @@ async fn test_body_deflate() {
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!(utils::deflate::decode(bytes), STR.as_bytes());
srv.stop().await; srv.stop().await;
} }
@ -360,7 +340,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
@ -368,7 +348,7 @@ async fn test_body_brotli() {
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!(brotli::decode(bytes), STR.as_bytes()); assert_eq!(utils::brotli::decode(bytes), STR.as_bytes());
srv.stop().await; srv.stop().await;
} }
@ -383,7 +363,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
@ -391,7 +371,7 @@ async fn test_body_zstd() {
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!(zstd::decode(bytes), STR.as_bytes()); assert_eq!(utils::zstd::decode(bytes), STR.as_bytes());
srv.stop().await; srv.stop().await;
} }
@ -409,7 +389,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
@ -417,7 +397,7 @@ async fn test_body_zstd_streaming() {
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!(zstd::decode(bytes), STR.as_bytes()); assert_eq!(utils::zstd::decode(bytes), STR.as_bytes());
srv.stop().await; srv.stop().await;
} }
@ -432,8 +412,8 @@ 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(utils::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,8 +443,8 @@ 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(utils::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,8 +464,8 @@ 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(utils::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,8 +485,8 @@ 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(utils::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,8 +507,8 @@ 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(utils::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,8 +534,8 @@ 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(utils::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,8 +555,8 @@ 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(utils::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,8 +577,8 @@ 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(utils::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,8 +604,8 @@ 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(utils::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,8 +626,8 @@ 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(utils::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,8 +657,8 @@ 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(utils::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 +677,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)
}))) })))
}); });
@ -706,7 +687,7 @@ async fn test_brotli_encoding_large_openssl() {
let mut res = srv let mut res = srv
.post("/") .post("/")
.append_header((header::CONTENT_ENCODING, "br")) .append_header((header::CONTENT_ENCODING, "br"))
.send_body(brotli::encode(&data)) .send_body(utils::brotli::encode(&data))
.await .await
.unwrap(); .unwrap();
assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.status(), StatusCode::OK);
@ -758,16 +739,20 @@ 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(utils::deflate::encode(&data)),
1024,
));
let mut res = req.await.unwrap(); let mut res = req.await.unwrap();
assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.status(), StatusCode::OK);
@ -931,14 +916,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
}
}