use headers::{HeaderMap, HeaderValue};
use hyper::{Body, Request, Response};
use std::ffi::OsStr;
use std::fs::Metadata;
use std::path::{Path, PathBuf};
use crate::compression;
use crate::fs::meta::try_metadata;
use crate::handler::RequestHandlerOpts;
use crate::headers_ext::ContentCoding;
use crate::Error;
pub struct CompressedFileVariant {
pub file_path: PathBuf,
pub metadata: Metadata,
pub encoding: ContentCoding,
}
pub fn init(enabled: bool, handler_opts: &mut RequestHandlerOpts) {
handler_opts.compression_static = enabled;
tracing::info!("compression static: enabled={enabled}");
}
pub(crate) fn post_process<T>(
opts: &RequestHandlerOpts,
_req: &Request<T>,
mut resp: Response<Body>,
) -> Result<Response<Body>, Error> {
if !opts.compression_static {
return Ok(resp);
}
let value = resp.headers().get(hyper::header::VARY).map_or(
HeaderValue::from_name(hyper::header::ACCEPT_ENCODING),
|h| {
let mut s = h.to_str().unwrap_or_default().to_owned();
s.push(',');
s.push_str(hyper::header::ACCEPT_ENCODING.as_str());
HeaderValue::from_str(s.as_str()).unwrap()
},
);
resp.headers_mut().insert(hyper::header::VARY, value);
Ok(resp)
}
pub fn precompressed_variant(
file_path: &Path,
headers: &HeaderMap<HeaderValue>,
) -> Option<CompressedFileVariant> {
tracing::trace!(
"preparing pre-compressed file variant path of {}",
file_path.display()
);
for encoding in compression::get_encodings(headers) {
let comp_ext = match encoding {
#[cfg(any(
feature = "compression",
feature = "compression-gzip",
feature = "compression-deflate"
))]
ContentCoding::GZIP | ContentCoding::DEFLATE => "gz",
#[cfg(any(feature = "compression", feature = "compression-brotli"))]
ContentCoding::BROTLI => "br",
#[cfg(any(feature = "compression", feature = "compression-zstd"))]
ContentCoding::ZSTD => "zst",
_ => {
tracing::trace!(
"preferred encoding based on the file extension was not determined, skipping"
);
continue;
}
};
let comp_name = match file_path.file_name().and_then(OsStr::to_str) {
Some(v) => v,
None => {
tracing::trace!("file name was not determined for the current path, skipping");
continue;
}
};
let file_path = file_path.with_file_name([comp_name, ".", comp_ext].concat());
tracing::trace!(
"trying to get the pre-compressed file variant metadata for {}",
file_path.display()
);
let (metadata, is_dir) = match try_metadata(&file_path) {
Ok(v) => v,
Err(e) => {
tracing::trace!("pre-compressed file variant error: {:?}", e);
continue;
}
};
if is_dir {
tracing::trace!("pre-compressed file variant found but it's a directory, skipping");
continue;
}
tracing::trace!("pre-compressed file variant found, serving it directly");
return Some(CompressedFileVariant {
file_path,
metadata,
encoding,
});
}
None
}