hyper_staticfile_jsutf8/util/
file_response_builder.rs1use super::{FileBytesStream, FileBytesStreamMultiRange, FileBytesStreamRange};
2use http::response::Builder as ResponseBuilder;
3use http::{header, HeaderMap, Method, Request, Response, Result, StatusCode};
4use http_range::HttpRange;
5use http_range::HttpRangeParseError;
6use hyper::Body;
7use rand::prelude::{thread_rng, SliceRandom};
8use std::fs::Metadata;
9use std::time::{Duration, SystemTime, UNIX_EPOCH};
10use tokio::fs::File;
11
12const MIN_VALID_MTIME: Duration = Duration::from_secs(2);
18
19const BOUNDARY_LENGTH: usize = 60;
20const BOUNDARY_CHARS: &[u8] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
21
22#[derive(Clone, Debug, Default)]
28pub struct FileResponseBuilder {
29 pub cache_headers: Option<u32>,
31 pub is_head: bool,
33 pub if_modified_since: Option<SystemTime>,
35 pub range: Option<String>,
37 pub if_range: Option<String>,
39}
40
41impl FileResponseBuilder {
42 pub fn new() -> Self {
44 Self::default()
45 }
46
47 pub fn request<B>(&mut self, req: &Request<B>) -> &mut Self {
49 self.request_parts(req.method(), req.headers())
50 }
51
52 pub fn request_parts(&mut self, method: &Method, headers: &HeaderMap) -> &mut Self {
54 self.request_method(method);
55 self.request_headers(headers);
56 self
57 }
58
59 pub fn request_method(&mut self, method: &Method) -> &mut Self {
61 self.is_head = *method == Method::HEAD;
62 self
63 }
64
65 pub fn request_headers(&mut self, headers: &HeaderMap) -> &mut Self {
67 self.if_modified_since_header(headers.get(header::IF_MODIFIED_SINCE));
68 self.range_header(headers.get(header::RANGE));
69 self.if_range(headers.get(header::IF_RANGE));
70 self
71 }
72
73 pub fn cache_headers(&mut self, value: Option<u32>) -> &mut Self {
75 self.cache_headers = value;
76 self
77 }
78
79 pub fn is_head(&mut self, value: bool) -> &mut Self {
81 self.is_head = value;
82 self
83 }
84
85 pub fn if_modified_since(&mut self, value: Option<SystemTime>) -> &mut Self {
87 self.if_modified_since = value;
88 self
89 }
90
91 pub fn if_modified_since_header(&mut self, value: Option<&header::HeaderValue>) -> &mut Self {
93 self.if_modified_since = value
94 .and_then(|v| v.to_str().ok())
95 .and_then(|v| httpdate::parse_http_date(v).ok());
96 self
97 }
98
99 pub fn if_range(&mut self, value: Option<&header::HeaderValue>) -> &mut Self {
101 if let Some(s) = value.and_then(|s| s.to_str().ok()) {
102 self.if_range = Some(s.to_string());
103 }
104 self
105 }
106
107 pub fn range_header(&mut self, value: Option<&header::HeaderValue>) -> &mut Self {
109 self.range = value.and_then(|v| v.to_str().ok()).map(|v| v.to_string());
110 self
111 }
112
113 pub fn build(
115 &self,
116 file: File,
117 metadata: Metadata,
118 content_type: String,
119 ) -> Result<Response<Body>> {
120 let mut res = ResponseBuilder::new();
121
122 let modified = metadata.modified().ok().filter(|v| {
124 v.duration_since(UNIX_EPOCH)
125 .ok()
126 .filter(|v| v >= &MIN_VALID_MTIME)
127 .is_some()
128 });
129
130 let mut range_cond_ok = self.if_range.is_none();
133 if let Some(modified) = modified {
134 if let Ok(modified_unix) = modified.duration_since(UNIX_EPOCH) {
135 if let Some(Ok(ims_unix)) =
138 self.if_modified_since.map(|v| v.duration_since(UNIX_EPOCH))
139 {
140 if modified_unix.as_secs() <= ims_unix.as_secs() {
141 return ResponseBuilder::new()
142 .status(StatusCode::NOT_MODIFIED)
143 .body(Body::empty());
144 }
145 }
146
147 let etag = format!(
148 "W/\"{0:x}-{1:x}.{2:x}\"",
149 metadata.len(),
150 modified_unix.as_secs(),
151 modified_unix.subsec_nanos()
152 );
153 if let Some(ref v) = self.if_range {
154 if *v == etag {
155 range_cond_ok = true;
156 }
157 }
158
159 res = res.header(header::ETAG, etag);
160 }
161
162 let last_modified_formatted = httpdate::fmt_http_date(modified);
163 if let Some(ref v) = self.if_range {
164 if *v == last_modified_formatted {
165 range_cond_ok = true;
166 }
167 }
168
169 res = res
170 .header(header::LAST_MODIFIED, last_modified_formatted)
171 .header(header::ACCEPT_RANGES, "bytes");
172 }
173
174 if let Some(seconds) = self.cache_headers {
176 res = res.header(
177 header::CACHE_CONTROL,
178 format!("public, max-age={}", seconds),
179 );
180 }
181
182 if self.is_head {
183 res = res.header(header::CONTENT_LENGTH, format!("{}", metadata.len()));
184 return res.status(StatusCode::OK).body(Body::empty());
185 }
186
187 let ranges = self.range.as_ref().filter(|_| range_cond_ok).and_then(|r| {
188 match HttpRange::parse(r, metadata.len()) {
189 Ok(r) => Some(Ok(r)),
190 Err(HttpRangeParseError::NoOverlap) => Some(Err(())),
191 Err(HttpRangeParseError::InvalidRange) => None,
192 }
193 });
194
195 if let Some(ranges) = ranges {
196 let ranges = match ranges {
197 Ok(r) => r,
198 Err(()) => {
199 return res
200 .status(StatusCode::RANGE_NOT_SATISFIABLE)
201 .body(Body::empty());
202 }
203 };
204
205 if ranges.len() == 1 {
206 let single_span = ranges[0];
207 res = res
208 .header(
209 header::CONTENT_RANGE,
210 content_range_header(&single_span, metadata.len()),
211 )
212 .header(header::CONTENT_LENGTH, format!("{}", single_span.length));
213
214 let body_stream = FileBytesStreamRange::new(file, single_span);
215 return res
216 .status(StatusCode::PARTIAL_CONTENT)
217 .body(body_stream.into_body());
218 } else if ranges.len() > 1 {
219 let mut boundary_tmp = [0u8; BOUNDARY_LENGTH];
220
221 let mut rng = thread_rng();
222 for v in boundary_tmp.iter_mut() {
223 *v = *BOUNDARY_CHARS.choose(&mut rng).unwrap();
225 }
226
227 let boundary = std::str::from_utf8(&boundary_tmp[..]).unwrap().to_string();
229
230 res = res.header(
231 hyper::header::CONTENT_TYPE,
232 format!("multipart/byteranges; boundary={}", boundary),
233 );
234
235 let mut body_stream =
236 FileBytesStreamMultiRange::new(file, ranges, boundary, metadata.len());
237 if !content_type.is_empty() {
238 body_stream.set_content_type(&content_type);
239 }
240
241 res = res.header(
242 hyper::header::CONTENT_LENGTH,
243 format!("{}", body_stream.compute_length()),
244 );
245
246 return res
247 .status(StatusCode::PARTIAL_CONTENT)
248 .body(body_stream.into_body());
249 }
250 }
251
252 res = res.header(header::CONTENT_LENGTH, format!("{}", metadata.len()));
253 if !content_type.is_empty() {
254 res = res.header(header::CONTENT_TYPE, content_type);
255 }
256
257 res.status(StatusCode::OK)
259 .body(FileBytesStream::new_with_limit(file, metadata.len()).into_body())
260 }
261}
262
263fn content_range_header(r: &HttpRange, total_length: u64) -> String {
264 format!(
265 "bytes {}-{}/{}",
266 r.start,
267 r.start + r.length - 1,
268 total_length
269 )
270}