rust_web_server/app/controller/static_resource/
mod.rs1use std::env;
2use std::fs::{File, metadata};
3use file_ext::FileExt;
4use crate::controller::Controller;
5use crate::header::Header;
6use crate::mime_type::MimeType;
7use crate::range::{ContentRange, Range};
8use crate::request::{METHOD, Request};
9use crate::response::{Error, Response, STATUS_CODE_REASON_PHRASE};
10use crate::server::ConnectionInfo;
11use crate::symbol::SYMBOL;
12use crate::url::URL;
13
14#[cfg(test)]
15mod tests;
16
17pub struct StaticResourceController;
18
19impl Controller for StaticResourceController {
20 fn is_matching(request: &Request, _connection: &ConnectionInfo) -> bool {
21 if request.method != METHOD.get {
22 return false;
23 }
24
25 let url_array = ["http://", "localhost", &request.request_uri];
26 let url = url_array.join(SYMBOL.empty_string);
27
28 let boxed_url_components = URL::parse(&url);
29 if boxed_url_components.is_err() {
30 let message = boxed_url_components.as_ref().err().unwrap().to_string();
31 println!("unexpected error, {}", message);
33 }
34
35 let components = boxed_url_components.unwrap();
36
37 let os_specific_separator : String = FileExt::get_path_separator();
38 let os_specific_path = &components.path.replace(SYMBOL.slash, os_specific_separator.as_str());
39
40 let boxed_static_filepath = FileExt::get_static_filepath(&os_specific_path);
41 if boxed_static_filepath.is_err() {
42 return false
43 }
44
45 let static_filepath = boxed_static_filepath.unwrap();
46
47 let mut is_directory_with_index_html = false;
48
49 let boxed_md = metadata(&static_filepath);
50 if boxed_md.is_ok() {
51 let md = boxed_md.unwrap();
52 if md.is_dir() {
53 let mut directory_index : String = "index.html".to_string();
54
55 let last_char = components.path.chars().last().unwrap();
56 if last_char != '/' {
57 let index : String = "index.html".to_string();
58 directory_index = format!("{}{}", os_specific_separator, index);
59
60 }
61 let index_html_in_directory = format!("{}{}", static_filepath, directory_index);
62
63
64 let boxed_file = File::open(&index_html_in_directory);
65 if boxed_file.is_err() {
66 return false
67 }
68
69 is_directory_with_index_html = true;
70 }
71 }
72
73
74
75 let boxed_file = File::open(&static_filepath);
76
77 let is_get = request.method == METHOD.get;
78 let is_head = request.method == METHOD.head;
79 let is_options = request.method == METHOD.options;
80
81 let is_matching_method = (is_get || is_head || is_options) && (request.request_uri != SYMBOL.slash);
82
83 if boxed_file.is_ok() || is_directory_with_index_html {
84 is_matching_method
85 } else {
86 if static_filepath.ends_with(".html") {
88 return false
89 }
90
91 let html_suffix = ".html";
92 let html_file = [&components.path.replace(SYMBOL.slash, &FileExt::get_path_separator()), html_suffix].join(SYMBOL.empty_string);
93 let boxed_static_filepath = FileExt::get_static_filepath(&html_file);
94 if boxed_static_filepath.is_err() {
95 return false
96 }
97
98 let static_filepath = boxed_static_filepath.unwrap();
99 let boxed_file = File::open(&static_filepath);
100
101 boxed_file.is_ok() && is_matching_method
102 }
103
104 }
105
106 fn process(request: &Request, mut response: Response, _connection: &ConnectionInfo) -> Response {
107 let boxed_content_range_list = StaticResourceController::process_static_resources(&request);
108 if boxed_content_range_list.is_ok() {
109 let content_range_list = boxed_content_range_list.unwrap();
110
111 if content_range_list.len() != 0 {
112
113 let mut status_code_reason_phrase = STATUS_CODE_REASON_PHRASE.n200_ok;
114
115 let does_request_include_range_header = request.get_header(Header::_RANGE.to_string()).is_some();
116 if does_request_include_range_header {
117 status_code_reason_phrase = STATUS_CODE_REASON_PHRASE.n206_partial_content;
118 }
119
120 let is_options_request = request.method == METHOD.options;
121 if is_options_request {
122 status_code_reason_phrase = STATUS_CODE_REASON_PHRASE.n204_no_content;
123 }
124
125
126 let dir = env::current_dir().unwrap();
127 let working_directory = dir.as_path().to_str().unwrap();
128
129 let url_array = ["http://", "localhost", &request.request_uri];
130 let url = url_array.join(SYMBOL.empty_string);
131
132 let boxed_url_components = URL::parse(&url);
133 if boxed_url_components.is_err() {
134 let message = boxed_url_components.as_ref().err().unwrap().to_string();
135 println!("unexpected error, {}", message);
137 }
138
139 let components = boxed_url_components.unwrap();
140
141 let static_filepath = [working_directory, components.path.as_str()].join(SYMBOL.empty_string);
142 let boxed_modified_date_time = FileExt::file_modified_utc(&static_filepath);
143
144 if boxed_modified_date_time.is_ok() {
145 let modified_date_time = boxed_modified_date_time.unwrap();
146 let last_modified_unix_nanos = Header{ name: Header::_LAST_MODIFIED_UNIX_EPOCH_NANOS.to_string(), value: modified_date_time.to_string() };
147 response.headers.push(last_modified_unix_nanos);
148
149 let file_size = metadata(&static_filepath).map(|m| m.len()).unwrap_or(0);
150 let etag_value = format!("\"{}-{}\"", modified_date_time, file_size);
151
152 let if_none_match = request.get_header(Header::_IF_NONE_MATCH.to_string());
153 if let Some(inm) = if_none_match {
154 if inm.value == etag_value || inm.value == "*" {
155 response.status_code = *STATUS_CODE_REASON_PHRASE.n304_not_modified.status_code;
156 response.reason_phrase = STATUS_CODE_REASON_PHRASE.n304_not_modified.reason_phrase.to_string();
157 response.headers.push(Header { name: Header::_ETAG.to_string(), value: etag_value });
158 return response;
159 }
160 }
161
162 response.headers.push(Header { name: Header::_ETAG.to_string(), value: etag_value });
163
164 const STREAM_THRESHOLD: u64 = 8 * 1024 * 1024;
167 let is_range_request = request.get_header(Header::_RANGE.to_string()).is_some();
168 if file_size > STREAM_THRESHOLD && !is_range_request {
169 let mime = MimeType::detect_mime_type(&static_filepath);
170 response.headers.push(Header {
171 name: Header::_CONTENT_TYPE.to_string(),
172 value: mime,
173 });
174 response.headers.push(Header {
175 name: Header::_CONTENT_LENGTH.to_string(),
176 value: file_size.to_string(),
177 });
178 response.status_code = *status_code_reason_phrase.status_code;
179 response.reason_phrase = status_code_reason_phrase.reason_phrase.to_string();
180 response.stream_file = Some(static_filepath);
181 return response;
182 }
183 }
184
185 response.status_code = *status_code_reason_phrase.status_code;
186 response.reason_phrase = status_code_reason_phrase.reason_phrase.to_string();
187 response.content_range_list = content_range_list;
188
189 }
190 } else {
191 let error : Error = boxed_content_range_list.err().unwrap();
192 let body = error.message;
193
194 let content_range = Range::get_content_range(
195 Vec::from(body.as_bytes()),
196 MimeType::TEXT_HTML.to_string()
197 );
198
199 let content_range_list = vec![content_range];
200
201 response.status_code = *error.status_code_reason_phrase.status_code;
202 response.reason_phrase = error.status_code_reason_phrase.reason_phrase.to_string();
203 response.content_range_list = content_range_list;
204
205 }
206
207
208 response
209 }
210}
211
212impl StaticResourceController {
214
215 pub fn is_matching_request(request: &Request) -> bool {
216 let boxed_static_filepath = FileExt::get_static_filepath(&request.request_uri);
217 if boxed_static_filepath.is_err() {
218 return false
219 }
220
221 let static_filepath = boxed_static_filepath.unwrap();
222
223 let boxed_md = metadata(&static_filepath);
224 if boxed_md.is_err() {
225 return false
226 }
227
228 let md = boxed_md.unwrap();
229 if md.is_dir() {
230 return false
231 }
232
233 let boxed_file = File::open(&static_filepath);
234
235 let is_get = request.method == METHOD.get;
236 let is_head = request.method == METHOD.head;
237 let is_options = request.method == METHOD.options;
238 boxed_file.is_ok() && (is_get || is_head || is_options && request.request_uri != SYMBOL.slash)
239 }
240
241 pub fn process_request(request: &Request, mut response: Response) -> Response {
242 let boxed_content_range_list = StaticResourceController::process_static_resources(&request);
243 if boxed_content_range_list.is_ok() {
244 let content_range_list = boxed_content_range_list.unwrap();
245
246 if content_range_list.len() != 0 {
247
248 let mut status_code_reason_phrase = STATUS_CODE_REASON_PHRASE.n200_ok;
249
250 let does_request_include_range_header = request.get_header(Header::_RANGE.to_string()).is_some();
251 if does_request_include_range_header {
252 status_code_reason_phrase = STATUS_CODE_REASON_PHRASE.n206_partial_content;
253 }
254
255 let is_options_request = request.method == METHOD.options;
256 if is_options_request {
257 status_code_reason_phrase = STATUS_CODE_REASON_PHRASE.n204_no_content;
258 }
259
260
261 let dir = env::current_dir().unwrap();
262 let working_directory = dir.as_path().to_str().unwrap();
263 let static_filepath = [working_directory, request.request_uri.as_str()].join(SYMBOL.empty_string);
264 let boxed_modified_date_time = FileExt::file_modified_utc(&static_filepath);
265
266 if boxed_modified_date_time.is_ok() {
267 let modified_date_time = boxed_modified_date_time.unwrap();
268 let last_modified_unix_nanos = Header{ name: Header::_LAST_MODIFIED_UNIX_EPOCH_NANOS.to_string(), value: modified_date_time.to_string() };
269 response.headers.push(last_modified_unix_nanos);
270
271 let file_size = metadata(&static_filepath).map(|m| m.len()).unwrap_or(0);
272 let etag_value = format!("\"{}-{}\"", modified_date_time, file_size);
273
274 let if_none_match = request.get_header(Header::_IF_NONE_MATCH.to_string());
275 if let Some(inm) = if_none_match {
276 if inm.value == etag_value || inm.value == "*" {
277 response.status_code = *STATUS_CODE_REASON_PHRASE.n304_not_modified.status_code;
278 response.reason_phrase = STATUS_CODE_REASON_PHRASE.n304_not_modified.reason_phrase.to_string();
279 response.headers.push(Header { name: Header::_ETAG.to_string(), value: etag_value });
280 return response;
281 }
282 }
283
284 response.headers.push(Header { name: Header::_ETAG.to_string(), value: etag_value });
285
286 const STREAM_THRESHOLD: u64 = 8 * 1024 * 1024;
287 let is_range_request = request.get_header(Header::_RANGE.to_string()).is_some();
288 if file_size > STREAM_THRESHOLD && !is_range_request {
289 let mime = MimeType::detect_mime_type(&static_filepath);
290 response.headers.push(Header {
291 name: Header::_CONTENT_TYPE.to_string(),
292 value: mime,
293 });
294 response.headers.push(Header {
295 name: Header::_CONTENT_LENGTH.to_string(),
296 value: file_size.to_string(),
297 });
298 response.status_code = *status_code_reason_phrase.status_code;
299 response.reason_phrase = status_code_reason_phrase.reason_phrase.to_string();
300 response.stream_file = Some(static_filepath);
301 return response;
302 }
303 }
304
305 response.status_code = *status_code_reason_phrase.status_code;
306 response.reason_phrase = status_code_reason_phrase.reason_phrase.to_string();
307 response.content_range_list = content_range_list;
308
309 }
310 } else {
311 let error : Error = boxed_content_range_list.err().unwrap();
312 let body = error.message;
313
314 let content_range = Range::get_content_range(
315 Vec::from(body.as_bytes()),
316 MimeType::TEXT_HTML.to_string()
317 );
318
319 let content_range_list = vec![content_range];
320
321 response.status_code = *error.status_code_reason_phrase.status_code;
322 response.reason_phrase = error.status_code_reason_phrase.reason_phrase.to_string();
323 response.content_range_list = content_range_list;
324
325 }
326
327
328 response
329 }
330
331 pub fn process_static_resources(request: &Request) -> Result<Vec<ContentRange>, Error> {
332 let dir = env::current_dir().unwrap();
333 let working_directory = dir.as_path().to_str().unwrap();
334
335 let url_array = ["http://", "localhost", &request.request_uri];
336 let url = url_array.join(SYMBOL.empty_string);
337
338 let boxed_url_components = URL::parse(&url);
339 if boxed_url_components.is_err() {
340 let message = boxed_url_components.as_ref().err().unwrap().to_string();
341 println!("unexpected error, {}", message);
343 }
344
345 let components = boxed_url_components.unwrap();
346
347 let os_specific_separator : String = FileExt::get_path_separator();
348 let os_specific_path = &components.path.replace(SYMBOL.slash, os_specific_separator.as_str());
349
350 let boxed_static_filepath = FileExt::get_static_filepath(&os_specific_path);
351
352 let static_filepath = boxed_static_filepath.unwrap();
353
354 let mut content_range_list = Vec::new();
355
356
357 let mut boxed_md = metadata(&static_filepath);
358 if boxed_md.is_err() {
359 let dot_html = format!("{}{}", &static_filepath, ".html");
360 boxed_md = metadata(&dot_html);
361
362 if boxed_md.is_err() {
363 let slash_index_html = format!("{}{}{}", &static_filepath, os_specific_separator, "index.html");
364 boxed_md = metadata(&slash_index_html);
365 }
366 }
367 if boxed_md.is_ok() {
368 let md = boxed_md.unwrap();
369
370 if md.is_dir() {
371 let mut range_header = &Header {
372 name: Header::_RANGE.to_string(),
373 value: "bytes=0-".to_string()
374 };
375
376 let boxed_header = request.get_header(Header::_RANGE.to_string());
377 if boxed_header.is_some() {
378 range_header = boxed_header.unwrap();
379 }
380
381 let mut directory_index : String = "index.html".to_string();
382
383 let last_char = components.path.chars().last().unwrap();
384 if last_char != '/' {
385 let index : String = "index.html".to_string();
386 directory_index = format!("{}{}", os_specific_separator, index);
387 }
388 let index_html_in_directory = format!("{}{}", os_specific_path, directory_index);
389
390
391 let boxed_content_range_list = Range::get_content_range_list(&index_html_in_directory, range_header);
392 if boxed_content_range_list.is_ok() {
393 content_range_list = boxed_content_range_list.unwrap();
394 } else {
395 let error = boxed_content_range_list.err().unwrap();
396 return Err(error)
397 }
398
399 return Ok(content_range_list);
400 }
401
402 let boxed_file = File::open(&static_filepath);
403 if boxed_file.is_ok() {
404 let md = metadata(&static_filepath).unwrap();
405 if md.is_dir() {
406 let mut range_header = &Header {
407 name: Header::_RANGE.to_string(),
408 value: "bytes=0-".to_string()
409 };
410
411 let boxed_header = request.get_header(Header::_RANGE.to_string());
412 if boxed_header.is_some() {
413 range_header = boxed_header.unwrap();
414 }
415
416 let mut directory_index : String = "index.html".to_string();
417
418 let last_char = components.path.chars().last().unwrap();
419 if last_char != '/' {
420 let index : String = "index.html".to_string();
421 directory_index = format!("{}{}", os_specific_separator, index);
422 }
423 let index_html_in_directory = format!("{}{}", os_specific_path, directory_index);
424
425
426 let boxed_content_range_list = Range::get_content_range_list(&index_html_in_directory, range_header);
427 if boxed_content_range_list.is_ok() {
428 content_range_list = boxed_content_range_list.unwrap();
429 } else {
430 let error = boxed_content_range_list.err().unwrap();
431 return Err(error)
432 }
433 }
434
435 if md.is_file() {
436 let mut range_header = &Header {
437 name: Header::_RANGE.to_string(),
438 value: "bytes=0-".to_string()
439 };
440
441 let boxed_header = request.get_header(Header::_RANGE.to_string());
442 if boxed_header.is_some() {
443 range_header = boxed_header.unwrap();
444 }
445
446 let boxed_content_range_list = Range::get_content_range_list(&request.request_uri, range_header);
447 if boxed_content_range_list.is_ok() {
448 content_range_list = boxed_content_range_list.unwrap();
449 } else {
450 let error = boxed_content_range_list.err().unwrap();
451 return Err(error)
452 }
453 }
454 }
455
456
457 if boxed_file.is_err() {
458 let static_filepath = [working_directory, components.path.as_str(), ".html"].join(SYMBOL.empty_string);
460
461 let boxed_file = File::open(&static_filepath);
462 if boxed_file.is_ok() {
463 let md = metadata(&static_filepath).unwrap();
464 if md.is_file() {
465 let mut range_header = &Header {
466 name: Header::_RANGE.to_string(),
467 value: "bytes=0-".to_string()
468 };
469
470 let boxed_header = request.get_header(Header::_RANGE.to_string());
471 if boxed_header.is_some() {
472 range_header = boxed_header.unwrap();
473 }
474
475 let url_array = ["http://", "localhost", &request.request_uri];
476 let url = url_array.join(SYMBOL.empty_string);
477
478 let boxed_url_components = URL::parse(&url);
479 if boxed_url_components.is_err() {
480 let message = boxed_url_components.as_ref().err().unwrap().to_string();
481 println!("unexpected error, {}", message);
483 }
484
485 let components = boxed_url_components.unwrap();
486
487 let html_file = [components.path.as_str(), ".html"].join(SYMBOL.empty_string);
491 let boxed_content_range_list = Range::get_content_range_list(html_file.as_str(), range_header);
492 if boxed_content_range_list.is_ok() {
493 content_range_list = boxed_content_range_list.unwrap();
494 } else {
495 let error = boxed_content_range_list.err().unwrap();
496 return Err(error)
497 }
498 }
499 }
500 }
501 }
502
503
504 Ok(content_range_list)
505 }
506}