use headers::{HeaderMap, HeaderMapExt, HeaderValue};
use hyper::{Body, Request, Response};
use std::ffi::OsStr;
use std::fs::Metadata;
use std::path::{Path, PathBuf};
use crate::Error;
use crate::fs::meta::try_metadata;
use crate::handler::RequestHandlerOpts;
use crate::headers_ext::{AcceptEncoding, ContentCoding};
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()
);
if let Some(ref accept_encoding) = headers.typed_get::<AcceptEncoding>() {
for encoding in accept_encoding.sorted_encodings() {
let comp_ext = match encoding {
ContentCoding::GZIP | ContentCoding::DEFLATE => "gz",
ContentCoding::BROTLI => "br",
ContentCoding::ZSTD => "zst",
_ => {
tracing::trace!(
"preferred encoding based on the file extension was not determined, skipping"
);
continue;
}
};
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");
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
}