static_web_server/
control_headers.rs1use hyper::{Body, Request, Response, header::HeaderValue};
11
12use crate::{Error, handler::RequestHandlerOpts};
13
14static CACHE_CONTROL_ONE_HOUR: HeaderValue = HeaderValue::from_static("max-age=3600");
16static CACHE_CONTROL_ONE_DAY: HeaderValue = HeaderValue::from_static("max-age=86400");
17static CACHE_CONTROL_ONE_YEAR: HeaderValue = HeaderValue::from_static("max-age=31536000");
18
19const CACHE_EXT_ONE_HOUR: [&str; 4] = ["atom", "json", "rss", "xml"];
21const CACHE_EXT_ONE_YEAR: [&str; 32] = [
22 "avif", "bmp", "bz2", "css", "doc", "gif", "gz", "htc", "ico", "jpeg", "jpg", "js", "jxl",
23 "map", "mjs", "mp3", "mp4", "ogg", "ogv", "pdf", "png", "rar", "rtf", "tar", "tgz", "wav",
24 "weba", "webm", "webp", "woff", "woff2", "zip",
25];
26
27pub(crate) fn init(enabled: bool, handler_opts: &mut RequestHandlerOpts) {
28 handler_opts.cache_control_headers = enabled;
29 tracing::info!("cache control headers: enabled={enabled}");
30}
31
32pub(crate) fn post_process<T>(
34 opts: &RequestHandlerOpts,
35 req: &Request<T>,
36 mut resp: Response<Body>,
37) -> Result<Response<Body>, Error> {
38 if opts.cache_control_headers {
39 append_headers(req.uri().path(), &mut resp);
40 }
41 Ok(resp)
42}
43
44pub fn append_headers(uri: &str, resp: &mut Response<Body>) {
46 let header_value = get_cache_control_header(uri);
47 resp.headers_mut()
48 .insert("cache-control", header_value.clone());
49}
50
51#[inline(always)]
55fn get_file_extension(uri: &str) -> Option<&str> {
56 uri.rsplit_once('.').map(|(_, rest)| rest)
57}
58
59#[inline(always)]
61fn get_cache_control_header(uri: &str) -> &'static HeaderValue {
62 if let Some(extension) = get_file_extension(uri) {
63 if CACHE_EXT_ONE_HOUR.binary_search(&extension).is_ok() {
64 return &CACHE_CONTROL_ONE_HOUR;
65 } else if CACHE_EXT_ONE_YEAR.binary_search(&extension).is_ok() {
66 return &CACHE_CONTROL_ONE_YEAR;
67 }
68 }
69 &CACHE_CONTROL_ONE_DAY
70}
71
72#[cfg(test)]
73mod tests {
74 use hyper::{Body, Response, StatusCode};
75
76 use super::{CACHE_EXT_ONE_HOUR, CACHE_EXT_ONE_YEAR, append_headers, get_file_extension};
77
78 #[test]
79 fn headers_one_hour() {
80 let mut resp = Response::new(Body::empty());
81 *resp.status_mut() = StatusCode::OK;
82
83 for ext in CACHE_EXT_ONE_HOUR.iter() {
84 append_headers(&["/some.", ext].concat(), &mut resp);
85
86 let cache_control = resp.headers().get(http::header::CACHE_CONTROL).unwrap();
87 assert_eq!(resp.status(), StatusCode::OK);
88 assert_eq!(cache_control.to_str().unwrap(), "max-age=3600");
89 }
90 }
91
92 #[test]
93 fn headers_one_day_default() {
94 let mut resp = Response::new(Body::empty());
95 *resp.status_mut() = StatusCode::OK;
96
97 append_headers("/", &mut resp);
98
99 let cache_control = resp.headers().get(http::header::CACHE_CONTROL).unwrap();
100 assert_eq!(resp.status(), StatusCode::OK);
101 assert_eq!(cache_control.to_str().unwrap(), "max-age=86400");
102 }
103
104 #[test]
105 fn headers_one_year() {
106 let mut resp = Response::new(Body::empty());
107 *resp.status_mut() = StatusCode::OK;
108
109 for ext in CACHE_EXT_ONE_YEAR.iter() {
110 append_headers(&["/some.", ext].concat(), &mut resp);
111
112 let cache_control = resp.headers().get(http::header::CACHE_CONTROL).unwrap();
113 assert_eq!(resp.status(), StatusCode::OK);
114 assert_eq!(cache_control.to_str().unwrap(), "max-age=31536000");
115 }
116 }
117
118 #[test]
119 fn find_uri_extension() {
120 assert_eq!(get_file_extension("/potato.zip"), Some("zip"));
121 assert_eq!(get_file_extension("/potato."), Some(""));
122 assert_eq!(get_file_extension("/"), None);
123 }
124}