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