forked from static-web-server/static-web-server
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcontrol_headers.rs
121 lines (100 loc) · 4.11 KB
/
control_headers.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
// SPDX-License-Identifier: MIT OR Apache-2.0
// This file is part of Static Web Server.
// See https://static-web-server.net/ for more information
// Copyright (C) 2019-present Jose Quintana <joseluisq.net>
//! It provides an arbitrary `Cache-Control` headers functionality
//! for incoming requests based on a set of file types.
//!
use headers::{CacheControl, HeaderMapExt};
use hyper::{Body, Response};
// Cache-Control `max-age` variants
const MAX_AGE_ONE_HOUR: u64 = 60 * 60;
const MAX_AGE_ONE_DAY: u64 = 60 * 60 * 24;
const MAX_AGE_ONE_YEAR: u64 = 60 * 60 * 24 * 365;
// `Cache-Control` list of extensions
const CACHE_EXT_ONE_HOUR: [&str; 4] = ["atom", "json", "rss", "xml"];
const CACHE_EXT_ONE_YEAR: [&str; 32] = [
"avif", "bmp", "bz2", "css", "doc", "gif", "gz", "htc", "ico", "jpeg", "jpg", "js", "jxl",
"map", "mjs", "mp3", "mp4", "ogg", "ogv", "pdf", "png", "rar", "rtf", "tar", "tgz", "wav",
"weba", "webm", "webp", "woff", "woff2", "zip",
];
/// It appends a `Cache-Control` header to a response if that one is part of a set of file types.
pub fn append_headers(uri: &str, resp: &mut Response<Body>) {
// Default max-age value in seconds (one day)
let mut max_age = MAX_AGE_ONE_DAY;
if let Some(extension) = uri_file_extension(uri) {
if CACHE_EXT_ONE_HOUR.binary_search(&extension).is_ok() {
max_age = MAX_AGE_ONE_HOUR;
} else if CACHE_EXT_ONE_YEAR.binary_search(&extension).is_ok() {
max_age = MAX_AGE_ONE_YEAR;
}
}
let cache_control = CacheControl::new()
.with_public()
.with_max_age(duration_from_secs(max_age));
resp.headers_mut().typed_insert(cache_control);
}
/// It caps a duration value at ~136 years.
fn duration_from_secs(secs: u64) -> std::time::Duration {
std::time::Duration::from_secs(std::cmp::min(secs, u32::MAX as u64))
}
/// Gets the file extension for a URI.
///
/// This assumes the extension contains a single dot. e.g. for "/file.tar.gz" it returns "gz".
fn uri_file_extension(uri: &str) -> Option<&str> {
uri.rsplit_once('.').map(|(_, rest)| rest)
}
#[cfg(test)]
mod tests {
use hyper::{Body, Response, StatusCode};
use super::{
append_headers, uri_file_extension, CACHE_EXT_ONE_HOUR, CACHE_EXT_ONE_YEAR,
MAX_AGE_ONE_DAY, MAX_AGE_ONE_HOUR, MAX_AGE_ONE_YEAR,
};
#[tokio::test]
async fn headers_one_hour() {
let mut resp = Response::new(Body::empty());
*resp.status_mut() = StatusCode::OK;
for ext in CACHE_EXT_ONE_HOUR.iter() {
append_headers(&["/some.", ext].concat(), &mut resp);
let cache_control = resp.headers().get(http::header::CACHE_CONTROL).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
cache_control.to_str().unwrap(),
format!("public, max-age={MAX_AGE_ONE_HOUR}")
);
}
}
#[tokio::test]
async fn headers_one_day_default() {
let mut resp = Response::new(Body::empty());
*resp.status_mut() = StatusCode::OK;
append_headers("/", &mut resp);
let cache_control = resp.headers().get(http::header::CACHE_CONTROL).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
cache_control.to_str().unwrap(),
format!("public, max-age={MAX_AGE_ONE_DAY}")
);
}
#[tokio::test]
async fn headers_one_year() {
let mut resp = Response::new(Body::empty());
*resp.status_mut() = StatusCode::OK;
for ext in CACHE_EXT_ONE_YEAR.iter() {
append_headers(&["/some.", ext].concat(), &mut resp);
let cache_control = resp.headers().get(http::header::CACHE_CONTROL).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(
cache_control.to_str().unwrap(),
format!("public, max-age={MAX_AGE_ONE_YEAR}")
);
}
}
#[test]
fn find_uri_extension() {
assert_eq!(uri_file_extension("/potato.zip"), Some("zip"));
assert_eq!(uri_file_extension("/potato."), Some(""));
assert_eq!(uri_file_extension("/"), None);
}
}