rust_web_server/app/controller/static_resource/
mod.rs

1use 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            // unfallable
32            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            // check if file with same name and .html extension exists
87            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                    // unfallable
136                    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
150                response.status_code = *status_code_reason_phrase.status_code;
151                response.reason_phrase = status_code_reason_phrase.reason_phrase.to_string();
152                response.content_range_list = content_range_list;
153
154            }
155        } else {
156            let error : Error = boxed_content_range_list.err().unwrap();
157            let body = error.message;
158
159            let content_range = Range::get_content_range(
160                Vec::from(body.as_bytes()),
161                MimeType::TEXT_HTML.to_string()
162            );
163
164            let content_range_list = vec![content_range];
165
166            response.status_code = *error.status_code_reason_phrase.status_code;
167            response.reason_phrase = error.status_code_reason_phrase.reason_phrase.to_string();
168            response.content_range_list = content_range_list;
169
170        }
171
172
173        response
174    }
175}
176
177//backward compatability
178impl StaticResourceController {
179
180    pub fn is_matching_request(request: &Request) -> bool {
181        let boxed_static_filepath = FileExt::get_static_filepath(&request.request_uri);
182        if boxed_static_filepath.is_err() {
183            return false
184        }
185
186        let static_filepath = boxed_static_filepath.unwrap();
187
188        let boxed_md = metadata(&static_filepath);
189        if boxed_md.is_err() {
190            return false
191        }
192
193        let md = boxed_md.unwrap();
194        if md.is_dir() {
195            return false
196        }
197
198        let boxed_file = File::open(&static_filepath);
199
200        let is_get = request.method == METHOD.get;
201        let is_head = request.method == METHOD.head;
202        let is_options = request.method == METHOD.options;
203        boxed_file.is_ok() && (is_get || is_head || is_options && request.request_uri != SYMBOL.slash)
204    }
205
206    pub fn process_request(request: &Request, mut response: Response) -> Response {
207        let boxed_content_range_list = StaticResourceController::process_static_resources(&request);
208        if boxed_content_range_list.is_ok() {
209            let content_range_list = boxed_content_range_list.unwrap();
210
211            if content_range_list.len() != 0 {
212
213                let mut status_code_reason_phrase = STATUS_CODE_REASON_PHRASE.n200_ok;
214
215                let does_request_include_range_header = request.get_header(Header::_RANGE.to_string()).is_some();
216                if does_request_include_range_header {
217                    status_code_reason_phrase = STATUS_CODE_REASON_PHRASE.n206_partial_content;
218                }
219
220                let is_options_request = request.method == METHOD.options;
221                if is_options_request {
222                    status_code_reason_phrase = STATUS_CODE_REASON_PHRASE.n204_no_content;
223                }
224
225
226                let dir = env::current_dir().unwrap();
227                let working_directory = dir.as_path().to_str().unwrap();
228                let static_filepath = [working_directory, request.request_uri.as_str()].join(SYMBOL.empty_string);
229                let boxed_modified_date_time = FileExt::file_modified_utc(&static_filepath);
230
231                if boxed_modified_date_time.is_ok() {
232                    let modified_date_time = boxed_modified_date_time.unwrap();
233                    let last_modified_unix_nanos = Header{ name: Header::_LAST_MODIFIED_UNIX_EPOCH_NANOS.to_string(), value: modified_date_time.to_string() };
234                    response.headers.push(last_modified_unix_nanos);
235                }
236
237                response.status_code = *status_code_reason_phrase.status_code;
238                response.reason_phrase = status_code_reason_phrase.reason_phrase.to_string();
239                response.content_range_list = content_range_list;
240
241            }
242        } else {
243            let error : Error = boxed_content_range_list.err().unwrap();
244            let body = error.message;
245
246            let content_range = Range::get_content_range(
247                Vec::from(body.as_bytes()),
248                MimeType::TEXT_HTML.to_string()
249            );
250
251            let content_range_list = vec![content_range];
252
253            response.status_code = *error.status_code_reason_phrase.status_code;
254            response.reason_phrase = error.status_code_reason_phrase.reason_phrase.to_string();
255            response.content_range_list = content_range_list;
256
257        }
258
259
260        response
261    }
262
263    pub fn process_static_resources(request: &Request) -> Result<Vec<ContentRange>, Error> {
264        let dir = env::current_dir().unwrap();
265        let working_directory = dir.as_path().to_str().unwrap();
266
267        let url_array = ["http://", "localhost", &request.request_uri];
268        let url = url_array.join(SYMBOL.empty_string);
269
270        let boxed_url_components = URL::parse(&url);
271        if boxed_url_components.is_err() {
272            let message = boxed_url_components.as_ref().err().unwrap().to_string();
273            // unfallable
274            println!("unexpected error, {}", message);
275        }
276
277        let components = boxed_url_components.unwrap();
278
279        let os_specific_separator : String = FileExt::get_path_separator();
280        let os_specific_path = &components.path.replace(SYMBOL.slash, os_specific_separator.as_str());
281
282        let boxed_static_filepath = FileExt::get_static_filepath(&os_specific_path);
283
284        let static_filepath = boxed_static_filepath.unwrap();
285
286        let mut content_range_list = Vec::new();
287
288
289        let mut boxed_md = metadata(&static_filepath);
290        if boxed_md.is_err() {
291            let dot_html = format!("{}{}", &static_filepath, ".html");
292            boxed_md = metadata(&dot_html);
293
294            if boxed_md.is_err() {
295                let slash_index_html = format!("{}{}{}", &static_filepath, os_specific_separator,  "index.html");
296                boxed_md = metadata(&slash_index_html);
297            }
298        }
299        if boxed_md.is_ok() {
300            let md = boxed_md.unwrap();
301
302            if md.is_dir() {
303                let mut range_header = &Header {
304                    name: Header::_RANGE.to_string(),
305                    value: "bytes=0-".to_string()
306                };
307
308                let boxed_header = request.get_header(Header::_RANGE.to_string());
309                if boxed_header.is_some() {
310                    range_header = boxed_header.unwrap();
311                }
312
313                let mut directory_index : String = "index.html".to_string();
314
315                let last_char = components.path.chars().last().unwrap();
316                if last_char != '/' {
317                    let index : String = "index.html".to_string();
318                    directory_index = format!("{}{}", os_specific_separator, index);
319                }
320                let index_html_in_directory = format!("{}{}", os_specific_path, directory_index);
321
322
323                let boxed_content_range_list = Range::get_content_range_list(&index_html_in_directory, range_header);
324                if boxed_content_range_list.is_ok() {
325                    content_range_list = boxed_content_range_list.unwrap();
326                } else {
327                    let error = boxed_content_range_list.err().unwrap();
328                    return Err(error)
329                }
330
331                return Ok(content_range_list);
332            }
333
334            let boxed_file = File::open(&static_filepath);
335            if boxed_file.is_ok()  {
336                let md = metadata(&static_filepath).unwrap();
337                if md.is_dir() {
338                    let mut range_header = &Header {
339                        name: Header::_RANGE.to_string(),
340                        value: "bytes=0-".to_string()
341                    };
342
343                    let boxed_header = request.get_header(Header::_RANGE.to_string());
344                    if boxed_header.is_some() {
345                        range_header = boxed_header.unwrap();
346                    }
347
348                    let mut directory_index : String = "index.html".to_string();
349
350                    let last_char = components.path.chars().last().unwrap();
351                    if last_char != '/' {
352                        let index : String = "index.html".to_string();
353                        directory_index = format!("{}{}", os_specific_separator, index);
354                    }
355                    let index_html_in_directory = format!("{}{}", os_specific_path, directory_index);
356
357
358                    let boxed_content_range_list = Range::get_content_range_list(&index_html_in_directory, range_header);
359                    if boxed_content_range_list.is_ok() {
360                        content_range_list = boxed_content_range_list.unwrap();
361                    } else {
362                        let error = boxed_content_range_list.err().unwrap();
363                        return Err(error)
364                    }
365                }
366
367                if md.is_file() {
368                    let mut range_header = &Header {
369                        name: Header::_RANGE.to_string(),
370                        value: "bytes=0-".to_string()
371                    };
372
373                    let boxed_header = request.get_header(Header::_RANGE.to_string());
374                    if boxed_header.is_some() {
375                        range_header = boxed_header.unwrap();
376                    }
377
378                    let boxed_content_range_list = Range::get_content_range_list(&request.request_uri, range_header);
379                    if boxed_content_range_list.is_ok() {
380                        content_range_list = boxed_content_range_list.unwrap();
381                    } else {
382                        let error = boxed_content_range_list.err().unwrap();
383                        return Err(error)
384                    }
385                }
386            }
387
388
389            if boxed_file.is_err() {
390                //check if .html file exists
391                let static_filepath = [working_directory, components.path.as_str(), ".html"].join(SYMBOL.empty_string);
392
393                let boxed_file = File::open(&static_filepath);
394                if boxed_file.is_ok()  {
395                    let md = metadata(&static_filepath).unwrap();
396                    if md.is_file() {
397                        let mut range_header = &Header {
398                            name: Header::_RANGE.to_string(),
399                            value: "bytes=0-".to_string()
400                        };
401
402                        let boxed_header = request.get_header(Header::_RANGE.to_string());
403                        if boxed_header.is_some() {
404                            range_header = boxed_header.unwrap();
405                        }
406
407                        let url_array = ["http://", "localhost", &request.request_uri];
408                        let url = url_array.join(SYMBOL.empty_string);
409
410                        let boxed_url_components = URL::parse(&url);
411                        if boxed_url_components.is_err() {
412                            let message = boxed_url_components.as_ref().err().unwrap().to_string();
413                            // unfallable
414                            println!("unexpected error, {}", message);
415                        }
416
417                        let components = boxed_url_components.unwrap();
418
419                        // let html_file = [SYMBOL.slash, ].join(SYMBOL.empty_string);
420
421
422                        let html_file = [components.path.as_str(), ".html"].join(SYMBOL.empty_string);
423                        let boxed_content_range_list = Range::get_content_range_list(html_file.as_str(), range_header);
424                        if boxed_content_range_list.is_ok() {
425                            content_range_list = boxed_content_range_list.unwrap();
426                        } else {
427                            let error = boxed_content_range_list.err().unwrap();
428                            return Err(error)
429                        }
430                    }
431                }
432            }
433        }
434
435
436        Ok(content_range_list)
437    }
438}