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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
use super::FileBytesStream;
use crate::util::DateTimeHttp;
use chrono::{offset::Local as LocalTz, DateTime, SubsecRound};
use http::response::Builder as ResponseBuilder;
use http::{header, HeaderMap, Method, Request, Response, Result, StatusCode};
use hyper::Body;
use std::fs::Metadata;
use std::time::{Duration, UNIX_EPOCH};
use tokio::fs::File;
const MIN_VALID_MTIME: Duration = Duration::from_secs(2);
#[derive(Clone, Debug, Default)]
pub struct FileResponseBuilder {
pub cache_headers: Option<u32>,
pub is_head: bool,
pub if_modified_since: Option<DateTime<LocalTz>>,
}
impl FileResponseBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn request<B>(&mut self, req: &Request<B>) -> &mut Self {
self.request_parts(req.method(), req.headers())
}
pub fn request_parts(&mut self, method: &Method, headers: &HeaderMap) -> &mut Self {
self.request_method(method);
self.request_headers(headers);
self
}
pub fn request_method(&mut self, method: &Method) -> &mut Self {
self.is_head = *method == Method::HEAD;
self
}
pub fn request_headers(&mut self, headers: &HeaderMap) -> &mut Self {
self.if_modified_since_header(headers.get(header::IF_MODIFIED_SINCE));
self
}
pub fn cache_headers(&mut self, value: Option<u32>) -> &mut Self {
self.cache_headers = value;
self
}
pub fn is_head(&mut self, value: bool) -> &mut Self {
self.is_head = value;
self
}
pub fn if_modified_since(&mut self, value: Option<DateTime<LocalTz>>) -> &mut Self {
self.if_modified_since = value;
self
}
pub fn if_modified_since_header(&mut self, value: Option<&header::HeaderValue>) -> &mut Self {
self.if_modified_since = value
.and_then(|v| v.to_str().ok())
.and_then(|v| DateTime::parse_from_rfc2822(v).ok())
.map(|v| v.with_timezone(&LocalTz));
self
}
pub fn build(&self, file: File, metadata: Metadata) -> Result<Response<Body>> {
let mut res = ResponseBuilder::new();
let modified = metadata.modified().ok().filter(|v| {
v.duration_since(UNIX_EPOCH)
.ok()
.filter(|v| v >= &MIN_VALID_MTIME)
.is_some()
});
if let Some(modified) = modified {
let modified: DateTime<LocalTz> = modified.into();
match self.if_modified_since {
Some(v) if modified.trunc_subsecs(0) <= v.trunc_subsecs(0) => {
return ResponseBuilder::new()
.status(StatusCode::NOT_MODIFIED)
.body(Body::empty())
}
_ => {}
}
res = res
.header(header::LAST_MODIFIED, modified.to_http_date())
.header(
header::ETAG,
format!(
"W/\"{0:x}-{1:x}.{2:x}\"",
metadata.len(),
modified.timestamp(),
modified.timestamp_subsec_nanos()
),
);
}
res = res.header(header::CONTENT_LENGTH, format!("{}", metadata.len()));
if let Some(seconds) = self.cache_headers {
res = res.header(
header::CACHE_CONTROL,
format!("public, max-age={}", seconds),
);
}
res.body(if self.is_head {
Body::empty()
} else {
FileBytesStream::new(file).into_body()
})
}
}