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