static_web_server/
compression_static.rs1use headers::{HeaderMap, HeaderMapExt, HeaderValue};
10use hyper::{Body, Request, Response};
11use std::ffi::OsStr;
12use std::fs::Metadata;
13use std::path::{Path, PathBuf};
14
15use crate::Error;
16use crate::fs::meta::try_metadata;
17use crate::handler::RequestHandlerOpts;
18use crate::headers_ext::{AcceptEncoding, ContentCoding};
19
20pub struct CompressedFileVariant {
22 pub file_path: PathBuf,
24 pub metadata: Metadata,
26 pub encoding: ContentCoding,
28}
29
30pub fn init(enabled: bool, handler_opts: &mut RequestHandlerOpts) {
32 handler_opts.compression_static = enabled;
33 tracing::info!("compression static: enabled={enabled}");
34}
35
36pub(crate) fn post_process<T>(
38 opts: &RequestHandlerOpts,
39 _req: &Request<T>,
40 mut resp: Response<Body>,
41) -> Result<Response<Body>, Error> {
42 if !opts.compression_static {
43 return Ok(resp);
44 }
45
46 let value = resp.headers().get(hyper::header::VARY).map_or(
48 HeaderValue::from_name(hyper::header::ACCEPT_ENCODING),
49 |h| {
50 let mut s = h.to_str().unwrap_or_default().to_owned();
51 s.push(',');
52 s.push_str(hyper::header::ACCEPT_ENCODING.as_str());
53 HeaderValue::from_str(s.as_str()).unwrap()
54 },
55 );
56 resp.headers_mut().insert(hyper::header::VARY, value);
57
58 Ok(resp)
59}
60
61pub fn precompressed_variant(
63 file_path: &Path,
64 headers: &HeaderMap<HeaderValue>,
65) -> Option<CompressedFileVariant> {
66 tracing::trace!(
67 "preparing pre-compressed file variant path of {}",
68 file_path.display()
69 );
70 if let Some(ref accept_encoding) = headers.typed_get::<AcceptEncoding>() {
71 for encoding in accept_encoding.sorted_encodings() {
72 let comp_ext = match encoding {
74 ContentCoding::GZIP | ContentCoding::DEFLATE => "gz",
76 ContentCoding::BROTLI => "br",
78 ContentCoding::ZSTD => "zst",
80 _ => {
81 tracing::trace!(
82 "preferred encoding based on the file extension was not determined, skipping"
83 );
84 continue;
85 }
86 };
87
88 let Some(comp_name) = file_path.file_name().and_then(OsStr::to_str) else {
89 tracing::trace!("file name was not determined for the current path, skipping");
90 continue;
91 };
92
93 let file_path = file_path.with_file_name([comp_name, ".", comp_ext].concat());
94 tracing::trace!(
95 "trying to get the pre-compressed file variant metadata for {}",
96 file_path.display()
97 );
98
99 let (metadata, is_dir) = match try_metadata(&file_path) {
100 Ok(v) => v,
101 Err(e) => {
102 tracing::trace!("pre-compressed file variant error: {:?}", e);
103 continue;
104 }
105 };
106
107 if is_dir {
108 tracing::trace!("pre-compressed file variant found but it's a directory, skipping");
109 continue;
110 }
111
112 tracing::trace!("pre-compressed file variant found, serving it directly");
113
114 return Some(CompressedFileVariant {
115 file_path,
116 metadata,
117 encoding,
118 });
119 }
120 }
121
122 None
123}