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
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
177impl 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 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 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 println!("unexpected error, {}", message);
415 }
416
417 let components = boxed_url_components.unwrap();
418
419 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}