use headers::{HeaderMap, HeaderMapExt, HeaderValue};
use hyper::{Request, Response};
use std::ffi::OsStr;
use std::fs::Metadata;
use std::path::{Path, PathBuf};
use crate::Error;
use crate::body::Body;
use crate::exts::headers::{AcceptEncoding, ContentCoding};
use crate::exts::http::append_vary_accept_encoding;
use crate::fs::meta::try_metadata;
use crate::handler::RequestHandlerOpts;
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!(enabled, "compression static");
}
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);
}
append_vary_accept_encoding(&mut resp);
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()
);
let accept_encoding = headers.typed_get::<AcceptEncoding>()?;
accept_encoding.sorted_encodings().find_map(|encoding| {
let (file_path, metadata) = variant_metadata(file_path, encoding)?;
tracing::trace!("pre-compressed file variant found, serving it directly");
Some(CompressedFileVariant {
file_path,
metadata,
encoding,
})
})
}
#[cfg(feature = "mem-cache")]
pub(crate) fn precompressed_variants(file_path: &Path) -> Box<[ContentCoding]> {
[
ContentCoding::ZSTD,
ContentCoding::BROTLI,
ContentCoding::GZIP,
]
.into_iter()
.filter(|&encoding| variant_metadata(file_path, encoding).is_some())
.collect()
}
fn variant_metadata(file_path: &Path, encoding: ContentCoding) -> Option<(PathBuf, Metadata)> {
let comp_ext = match encoding {
ContentCoding::GZIP => "gz",
ContentCoding::BROTLI => "br",
ContentCoding::ZSTD => "zst",
_ => {
tracing::trace!(
"preferred encoding based on the file extension was not determined, skipping"
);
return None;
}
};
let Some(comp_name) = file_path.file_name().and_then(OsStr::to_str) else {
tracing::trace!("file name was not determined for the current path, skipping");
return None;
};
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);
return None;
}
};
if is_dir {
tracing::trace!("pre-compressed file variant found but it's a directory, skipping");
return None;
}
Some((file_path, metadata))
}