Add compression and logging middleware

main
idylls 2 years ago
parent e17d672954
commit cbcdeb3350
Signed by: idylls
GPG Key ID: 8A7167CBC2CC9F0F

94
Cargo.lock generated

@ -3,10 +3,25 @@
version = 3
[[package]]
name = "anyhow"
version = "1.0.58"
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "alloc-no-stdlib"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35ef4730490ad1c4eae5c4325b2a95f521d023e5c885853ff7aca0a6a1631db3"
[[package]]
name = "alloc-stdlib"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704"
checksum = "697ed7edc0f1711de49ce108c541623a0af97c6c60b2f6e2b65229847ac843c2"
dependencies = [
"alloc-no-stdlib",
]
[[package]]
name = "autocfg"
@ -20,6 +35,27 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "brotli"
version = "3.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
"brotli-decompressor",
]
[[package]]
name = "brotli-decompressor"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
]
[[package]]
name = "bumpalo"
version = "3.10.0"
@ -44,6 +80,15 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "crc32fast"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
dependencies = [
"cfg-if",
]
[[package]]
name = "encoding_rs"
version = "0.8.31"
@ -53,6 +98,16 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "flate2"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "fnv"
version = "1.0.7"
@ -294,6 +349,15 @@ version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
[[package]]
name = "miniz_oxide"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc"
dependencies = [
"adler",
]
[[package]]
name = "mio"
version = "0.8.4"
@ -527,16 +591,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "statue_rs"
name = "statue"
version = "0.1.0"
dependencies = [
"anyhow",
"brotli",
"flate2",
"hyper",
"num_cpus",
"reqwest",
"serde",
"serde_json",
"tokio",
"tower",
]
[[package]]
@ -621,6 +687,23 @@ dependencies = [
"tracing",
]
[[package]]
name = "tower"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
dependencies = [
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "tower-layer"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62"
[[package]]
name = "tower-service"
version = "0.3.2"
@ -634,6 +717,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307"
dependencies = [
"cfg-if",
"log",
"pin-project-lite",
"tracing-core",
]

@ -1,15 +1,17 @@
[package]
name = "statue_rs"
name = "statue"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.58"
brotli = "3.3.4"
flate2 = "1.0.24"
hyper = { version = "0.14.20", default-features = false, features = ["http1", "http2", "server", "tcp", "client"] }
num_cpus = "1.13.1"
reqwest = { version = "0.11.11", default-features = false, features = ["rustls-tls"] }
serde = { version = "1.0.140", features = ["derive"] }
serde_json = "1.0.82"
tokio = { version = "1.20.1", features = ["rt", "macros", "rt-multi-thread", "fs", "process"] }
tower = "0.4.13"

@ -1,16 +1,19 @@
mod api;
mod util;
use std::{collections::HashMap, net::ToSocketAddrs};
use std::{collections::BTreeMap, net::ToSocketAddrs};
use flate2::Compression;
use hyper::{
header::HeaderValue,
service::{make_service_fn, service_fn},
Body, Request as Request_, Response as Response_,
};
use serde::{Deserialize, Serialize};
use tower::ServiceBuilder;
type Request = Request_<Body>;
type Response = Response_<Body>;
type Response = Response_<String>;
pub type Result<T> = core::result::Result<T, std::io::Error>;
@ -27,16 +30,16 @@ struct ConfigSchema {
listen_addr: Option<String>,
title: Option<String>,
description: Option<String>,
hosts: HashMap<String, String>,
services: HashMap<String, HttpService>,
hosts: BTreeMap<String, String>,
services: BTreeMap<String, HttpService>,
}
pub struct Config {
listen_addr: String,
title: String,
description: String,
hosts: HashMap<String, String>,
services: HashMap<String, HttpService>,
hosts: BTreeMap<String, String>,
services: BTreeMap<String, HttpService>,
}
fn not_found() -> Result<Response> {
@ -56,9 +59,8 @@ fn internal_server_error() -> Result<Response> {
async fn index() -> Result<Response> {
let config = read_config().await?;
let mut resp = Response::new(
format!(
r###"
let mut resp = Response::new(format!(
r###"
<!DOCTYPE html>
<html lang="en">
<head>
@ -79,13 +81,11 @@ async fn index() -> Result<Response> {
</body>
</html>
"###,
title = config.title,
description = config.description,
style = include_str!("../frontend/style.css"),
script = include_str!(concat!(env!("OUT_DIR"), "/frontend.js")),
)
.into(),
);
title = config.title,
description = config.description,
style = include_str!("../frontend/style.css"),
script = include_str!(concat!(env!("OUT_DIR"), "/frontend.js")),
));
resp.headers_mut().insert(
"content-type",
HeaderValue::from_static("text/html; charset=utf-8"),
@ -97,7 +97,7 @@ async fn index() -> Result<Response> {
fn json<T: Serialize>(v: &T) -> Result<Response> {
let json = serde_json::to_string(&v)?;
let mut resp = Response::new(json.into());
let mut resp = Response::new(json);
resp.headers_mut()
.insert("content-type", HeaderValue::from_static("application/json"));
@ -157,19 +157,35 @@ pub async fn read_config() -> Result<Config> {
async fn serve() -> Result<()> {
let config = read_config().await?;
let addr = config.listen_addr.to_socket_addrs()?.next().unwrap();
let make_service = make_service_fn(|_conn| async {
Ok::<_, std::io::Error>(service_fn(|req| async {
let resp = route(req).await;
let service = service_fn(|req| async {
let resp = route(req).await;
match resp {
Ok(r) => Ok(r),
Err(e) => {
eprintln!("{}", e);
match resp {
Ok(r) => Ok(r),
Err(e) => {
eprintln!("{}", e);
internal_server_error()
}
internal_server_error()
}
}))
}
});
let make_service = make_service_fn(|_conn| async move {
let service = ServiceBuilder::new()
.layer(util::Log)
.layer(util::Compress {
algorithms: util::AlgorithmPreferences {
brotli: Some((3, util::brotli_default_params(4, true))),
gzip: Some((2, Compression::fast())),
deflate: Some((1, Compression::fast())),
},
predicate: util::CompressPredicate {
min_size: 256,
content_type_predicate: |_: &str| true,
},
})
.service(service);
Ok::<_, std::convert::Infallible>(service)
});
let server = hyper::server::Server::bind(&addr).serve(make_service);

@ -0,0 +1,669 @@
use std::{
cmp::Ordering,
future::Future,
pin::Pin,
task::{Context, Poll},
time::Instant,
};
use brotli::{enc::BrotliEncoderParams, CompressorWriter};
use flate2::{
write::{DeflateEncoder, GzEncoder},
Compression,
};
use hyper::{
body::{Buf, Bytes, HttpBody},
header::HeaderValue,
Method, Request, Response, Uri,
};
use tower::{Layer, Service};
#[derive(Clone)]
pub struct Log;
impl<S> Layer<S> for Log {
type Service = LogService<S>;
fn layer(&self, inner: S) -> Self::Service {
LogService::new(inner)
}
}
#[derive(Clone)]
pub struct LogService<S> {
inner: S,
}
impl<S> LogService<S> {
fn new(inner: S) -> Self {
Self { inner }
}
}
impl<S, ReqBody, ResBody> Service<Request<ReqBody>> for LogService<S>
where
S: Service<Request<ReqBody>, Response = Response<ResBody>>,
{
type Response = S::Response;
type Error = S::Error;
type Future = LogServiceFuture<S::Future>;
fn poll_ready(
&mut self,
cx: &mut Context<'_>,
) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, req: Request<ReqBody>) -> Self::Future {
let uri = req.uri().clone();
let method = req.method().clone();
let fut = self.inner.call(req);
LogServiceFuture {
inner: fut,
uri,
method,
start: None,
}
}
}
pub struct LogServiceFuture<InnerFut> {
uri: Uri,
method: Method,
inner: InnerFut,
start: Option<Instant>,
}
impl<ResBody, InnerFut, InnerFutError> Future for LogServiceFuture<InnerFut>
where
InnerFut: Future<Output = Result<Response<ResBody>, InnerFutError>>,
{
type Output = InnerFut::Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let (uri, method, inner, mut start) = unsafe {
let this = self.get_unchecked_mut();
(
&this.uri,
&this.method,
Pin::new_unchecked(&mut this.inner),
Pin::new_unchecked(&mut this.start),
)
};
start.get_or_insert(Instant::now());
match inner.poll(cx) {
Poll::Ready(r) => {
let res = match r {
Ok(res) => res,
e => return Poll::Ready(e),
};
let start = unsafe { start.unwrap_unchecked() };
let now = Instant::now();
let diff = now - start;
println!(
"{} {} {} [{}ms]",
method,
uri,
res.status(),
diff.as_millis()
);
Poll::Ready(Ok(res))
}
p => p,
}
}
}
pub type Preference = u8;
/// Compression algorithms and their associated prefence level. Higher is
/// better
#[derive(Clone)]
pub struct AlgorithmPreferences {
pub brotli: Option<(Preference, BrotliEncoderParams)>,
pub gzip: Option<(Preference, Compression)>,
pub deflate: Option<(Preference, Compression)>,
}
pub fn brotli_default_params(
quality: u8,
favor_cpu_efficiency: bool,
) -> BrotliEncoderParams {
BrotliEncoderParams {
quality: quality as i32,
favor_cpu_efficiency,
..Default::default()
}
}
#[derive(Clone)]
pub struct CompressPredicate<ContentTypePredicateFn> {
pub min_size: usize,
pub content_type_predicate: ContentTypePredicateFn,
}
#[derive(Clone)]
pub struct Compress<ContentTypePredicateFn> {
pub algorithms: AlgorithmPreferences,
pub predicate: CompressPredicate<ContentTypePredicateFn>,
}
impl<CTPF: Clone, S> Layer<S> for Compress<CTPF> {
type Service = CompressService<S, CTPF>;
fn layer(&self, inner: S) -> Self::Service {
CompressService {
compress: self.clone(),
inner,
}
}
}
#[derive(Clone)]
pub struct CompressService<S, CTPF> {
compress: Compress<CTPF>,
inner: S,
}
pub enum Algorithm {
Brotli(BrotliEncoderParams),
Deflate(Compression),
Gzip(Compression),
}
impl<S, CTPF, ReqBody, ResBody> Service<Request<ReqBody>>
for CompressService<S, CTPF>
where
S: Service<Request<ReqBody>, Response = Response<ResBody>>,
CTPF: Clone + FnOnce(&str) -> bool,
ResBody: HttpBody,
{
type Response = Response<CompressBody<ResBody>>;
type Error = S::Error;
type Future = CompressFuture<S::Future, CTPF>;
fn poll_ready(
&mut self,
cx: &mut Context<'_>,
) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, req: Request<ReqBody>) -> Self::Future {
let chosen_algorithm = match req.headers().get("accept-encoding") {
None => None,
Some(s) => choose_algorithm(&self.compress.algorithms, s),
};
let fut = self.inner.call(req);
CompressFuture {
inner: fut,
chosen_algorithm,
predicate: self.compress.predicate.clone(),
}
}
}
fn choose_algorithm(
preferences: &AlgorithmPreferences,
accept_encodings: &HeaderValue,
) -> Option<Algorithm> {
let accept_encodings = match accept_encodings.to_str().ok() {
None => return None,
Some(s) => s,
};
accept_encodings
.split(',')
.flat_map(|s| s.split(';').next())
.map(|s| s.trim().to_string())
.flat_map(|s| match s.as_str() {
"br" => preferences
.brotli
.as_ref()
.map(|p| (Algorithm::Brotli(p.1.clone()), p.0)),
"gzip" => preferences
.gzip
.as_ref()
.map(|p| (Algorithm::Gzip(p.1), p.0)),
"deflate" => preferences
.deflate
.as_ref()
.map(|p| (Algorithm::Deflate(p.1), p.0)),
_ => None,
})
.max_by(|a, b| {
if a.1 == b.1 {
return Ordering::Greater;
}
a.1.cmp(&b.1)
})
.map(|(e, _)| e)
}
pub struct CompressFuture<InnerFut, ContentTypePredicateFn> {
inner: InnerFut,
chosen_algorithm: Option<Algorithm>,
predicate: CompressPredicate<ContentTypePredicateFn>,
}
impl<InnerFut, CTPF, ResBody, InnerFutError> Future
for CompressFuture<InnerFut, CTPF>
where
InnerFut: Future<Output = Result<Response<ResBody>, InnerFutError>>,
CTPF: FnOnce(&str) -> bool + Clone,
ResBody: HttpBody,
{
type Output = Result<Response<CompressBody<ResBody>>, InnerFutError>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let (chosen_algorithm, predicate, inner) = unsafe {
let this = self.get_unchecked_mut();
(
&this.chosen_algorithm,
&mut this.predicate,
Pin::new_unchecked(&mut this.inner),
)
};
match inner.poll(cx) {
Poll::Ready(r) => {
let res = match r {
Ok(res) => res,
Err(e) => return Poll::Ready(Err(e)),
};
let res = choose_body(res, chosen_algorithm, predicate.clone());
Poll::Ready(Ok(res))
}
_ => Poll::Pending,
}
}
}
pub enum CompressBody<B> {
None(B),
Brotli(BrotliBody<B>),
Gzip(GzipBody<B>),
Deflate(DeflateBody<B>),
}
impl<B> HttpBody for CompressBody<B>
where
B: HttpBody,
B::Data: Send + 'static,
{
type Data = Box<dyn Buf + Send + 'static>;
type Error = B::Error;
fn poll_data(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Self::Data, Self::Error>>> {
match unsafe { self.get_unchecked_mut() } {
CompressBody::Brotli(b) => unsafe { Pin::new_unchecked(b) }
.poll_data(cx)
.map(|o| o.map(|r| r.map(|b| Box::new(b) as Box<_>))),
CompressBody::Gzip(b) => unsafe { Pin::new_unchecked(b) }
.poll_data(cx)
.map(|o| o.map(|r| r.map(|b| Box::new(b) as Box<_>))),
CompressBody::Deflate(b) => unsafe { Pin::new_unchecked(b) }
.poll_data(cx)
.map(|o| o.map(|r| r.map(|b| Box::new(b) as Box<_>))),
CompressBody::None(b) => unsafe { Pin::new_unchecked(b) }
.poll_data(cx)
.map(|o| o.map(|r| r.map(|b| Box::new(b) as Box<_>))),
}
}
fn poll_trailers(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Result<Option<hyper::HeaderMap>, Self::Error>> {
match unsafe { self.get_unchecked_mut() } {
CompressBody::None(b) => {
unsafe { Pin::new_unchecked(b) }.poll_trailers(cx)
}
CompressBody::Brotli(b) => {
unsafe { Pin::new_unchecked(b) }.poll_trailers(cx)
}
CompressBody::Gzip(b) => {
unsafe { Pin::new_unchecked(b) }.poll_trailers(cx)
}
CompressBody::Deflate(b) => {
unsafe { Pin::new_unchecked(b) }.poll_trailers(cx)
}
}
}
}
pub struct BrotliBody<B> {
inner: B,
compressor: Option<CompressorWriter<Vec<u8>>>,
}
impl<B: HttpBody> HttpBody for BrotliBody<B>
where
B::Data: Send + 'static,
{
type Data = Bytes;
type Error = B::Error;
fn poll_data(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Self::Data, Self::Error>>> {
let (inner, compressor) = unsafe {
let this = self.get_unchecked_mut();
(Pin::new_unchecked(&mut this.inner), &mut this.compressor)
};
if compressor.is_none() {
return Poll::Ready(None);
}
let data = match inner.poll_data(cx) {
Poll::Ready(Some(Ok(d))) => Some(d),
Poll::Ready(None) => None,
Poll::Ready(Some(Err(e))) => return Poll::Ready(Some(Err(e))),
Poll::Pending => return Poll::Pending,
};
match data {
None => {
let compressor =
unsafe { compressor.take().unwrap_unchecked() };
let buf = compressor.into_inner();
Poll::Ready(Some(Ok(Bytes::from(buf))))
}
Some(d) => {
let mut compressor =
unsafe { compressor.as_mut().unwrap_unchecked() };
let mut reader = d.reader();
use std::io::Write;
let _ = std::io::copy(&mut reader, &mut compressor);
let _ = compressor.flush();
let buf = compressor.get_ref().clone();
compressor.get_mut().clear();
Poll::Ready(Some(Ok(Bytes::from(buf))))
}
}
}
fn poll_trailers(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Result<Option<hyper::HeaderMap>, Self::Error>> {
let inner = unsafe {
let this = self.get_unchecked_mut();
Pin::new_unchecked(&mut this.inner)
};
inner.poll_trailers(cx)
}
}
impl<B> BrotliBody<B> {
pub fn new(inner: B, params: &BrotliEncoderParams) -> Self {
Self {
inner,
compressor: Some(CompressorWriter::with_params(
Vec::new(),
4096,
params,
)),
}
}
}
pub struct GzipBody<B> {
inner: B,
encoder: Option<GzEncoder<Vec<u8>>>,
}
impl<B> GzipBody<B> {
pub fn new(inner: B, compression: Compression) -> Self {
Self {
inner,
encoder: Some(GzEncoder::new(Vec::new(), compression)),
}
}
}
impl<B> HttpBody for GzipBody<B>
where
B: HttpBody,
{
type Data = Bytes;
type Error = B::Error;
fn poll_data(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Self::Data, Self::Error>>> {
let (inner, encoder) = unsafe {
let this = self.get_unchecked_mut();
(Pin::new_unchecked(&mut this.inner), &mut this.encoder)
};
if encoder.is_none() {
return Poll::Ready(None);
}
let data = match inner.poll_data(cx) {
Poll::Ready(Some(Ok(d))) => Some(d),
Poll::Ready(None) => None,
Poll::Ready(Some(Err(e))) => return Poll::Ready(Some(Err(e))),
Poll::Pending => return Poll::Pending,
};
match data {
None => {
let encoder = unsafe { encoder.take().unwrap_unchecked() };
let buf = encoder.finish().unwrap();
Poll::Ready(Some(Ok(Bytes::from(buf))))
}
Some(d) => {
let mut encoder =
unsafe { encoder.as_mut().unwrap_unchecked() };
let mut reader = d.reader();
let _ = std::io::copy(&mut reader, &mut encoder);
let buf = encoder.get_ref().clone();
encoder.get_mut().clear();
Poll::Ready(Some(Ok(Bytes::from(buf))))
}
}
}
fn poll_trailers(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Result<Option<hyper::HeaderMap>, Self::Error>> {
let inner = unsafe {
let this = self.get_unchecked_mut();
Pin::new_unchecked(&mut this.inner)
};
inner.poll_trailers(cx)
}
}
pub struct DeflateBody<B> {
inner: B,
encoder: Option<DeflateEncoder<Vec<u8>>>,
}
impl<B> DeflateBody<B> {
pub fn new(inner: B, compression: Compression) -> Self {
Self {
inner,
encoder: Some(DeflateEncoder::new(Vec::new(), compression)),
}
}
}
impl<B> HttpBody for DeflateBody<B>
where
B: HttpBody,
{
type Data = Bytes;
type Error = B::Error;
fn poll_data(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Self::Data, Self::Error>>> {
let (inner, encoder) = unsafe {
let this = self.get_unchecked_mut();
(Pin::new_unchecked(&mut this.inner), &mut this.encoder)
};
if encoder.is_none() {
return Poll::Ready(None);
}
let data = match inner.poll_data(cx) {
Poll::Ready(Some(Ok(d))) => Some(d),
Poll::Ready(None) => None,
Poll::Ready(Some(Err(e))) => return Poll::Ready(Some(Err(e))),
Poll::Pending => return Poll::Pending,
};
match data {
None => {
let encoder = unsafe { encoder.take().unwrap_unchecked() };
let buf = encoder.finish().unwrap();
Poll::Ready(Some(Ok(Bytes::from(buf))))
}
Some(d) => {
let mut encoder =
unsafe { encoder.as_mut().unwrap_unchecked() };
let mut reader = d.reader();
let _ = std::io::copy(&mut reader, &mut encoder);
let buf = encoder.get_ref().clone();
encoder.get_mut().clear();
Poll::Ready(Some(Ok(Bytes::from(buf))))
}
}
}
fn poll_trailers(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Result<Option<hyper::HeaderMap>, Self::Error>> {
let inner = unsafe {
let this = self.get_unchecked_mut();
Pin::new_unchecked(&mut this.inner)
};
inner.poll_trailers(cx)
}
}
fn replace_body<B, B_>(
r: Response<B>,
replace: impl FnOnce(B) -> B_,
) -> Response<B_> {
let (parts, body) = r.into_parts();
Response::from_parts(parts, replace(body))
}
pub fn choose_body<B, CTPF>(
res: Response<B>,
chosen_algorithm: &Option<Algorithm>,
predicate: CompressPredicate<CTPF>,
) -> Response<CompressBody<B>>
where
CTPF: FnOnce(&str) -> bool,
B: HttpBody,
{
let chosen_algorithm = match chosen_algorithm {
None => return replace_body(res, CompressBody::None),
Some(a) => a,
};
let headers = res.headers();
let content_length = match headers
.get("transfer-encoding")
.and_then(|v| v.to_str().ok())
{
None => Some(
headers
.get("content-length")
.and_then(|v| v.to_str().ok())
.and_then(|s| s.parse::<usize>().ok())
.unwrap_or_else(|| res.body().size_hint().lower() as usize),
),
Some("chunked") => None,
_ => return replace_body(res, CompressBody::None),
};
match content_length {
Some(l) if l < predicate.min_size => {
return replace_body(res, CompressBody::None)
}
_ => (),
}
let content_type =
match headers.get("content-type").and_then(|v| v.to_str().ok()) {
Some(ct) => ct,
None => return replace_body(res, CompressBody::None),
};
if !(predicate.content_type_predicate)(content_type) {
return replace_body(res, CompressBody::None);
}
match chosen_algorithm {
Algorithm::Brotli(p) => {
let mut res = replace_body(res, |b| {
CompressBody::Brotli(BrotliBody::new(b, p))
});
res.headers_mut()
.insert("content-encoding", HeaderValue::from_static("br"));
res
}
Algorithm::Gzip(c) => {
let mut res =
replace_body(res, |b| CompressBody::Gzip(GzipBody::new(b, *c)));
res.headers_mut()
.insert("content-encoding", HeaderValue::from_static("gzip"));
res
}
Algorithm::Deflate(c) => {
let mut res = replace_body(res, |b| {
CompressBody::Deflate(DeflateBody::new(b, *c))
});
res.headers_mut().insert(
"content-encoding",
HeaderValue::from_static("deflate"),
);
res
}
}
}
Loading…
Cancel
Save