1use std::net::SocketAddr;
2use std::thread;
3use file_ext::FileExt;
4use crate::entry_point::command_line_args::CommandLineArgument;
5use crate::request::Request;
6use crate::response::Response;
7
8#[cfg(test)]
9mod tests;
10
11pub struct Log;
17
18impl Log {
19 pub fn request_response(request: &Request, response: &Response, peer_addr: &SocketAddr) -> String {
20 let mut request_headers = "".to_string();
21 for header in &request.headers {
22 if &header.name.chars().count() > &0 {
23 request_headers = [
24 request_headers,
25 "\n ".to_string(),
26 header.name.to_string(),
27 ": ".to_string(),
28 header.value.to_string()
29 ].join("");
30 }
31 }
32
33 let mut response_headers = "".to_string();
34 for header in &response.headers {
35 if &header.name.chars().count() > &0 {
36 response_headers = [
37 response_headers,
38 "\n ".to_string(),
39 header.name.to_string(),
40 ": ".to_string(),
41 header.value.to_string()
42 ].join("");
43 }
44 }
45
46 let mut response_body_length = 0;
47 let mut response_body_parts_number = 0;
48 for content_range in &response.content_range_list {
49 let boxed_parse = content_range.size.parse::<i32>();
50 if boxed_parse.is_ok() {
51 response_body_length += boxed_parse.unwrap();
52 response_body_parts_number += 1;
53 }
54 }
55
56 let current_thread = thread::current();
57 let thread_id = current_thread.name().unwrap();
58
59 let log_request_response = format!("\n\nRequest (thread id: {} peer address is {}):\n {} {} {} {}\n Body: {} byte(s) total (including default initialization vector)\nEnd of Request\nResponse:\n {} {} {}\n\n Body: {} part(s), {} byte(s) total\nEnd of Response",
60 thread_id,
61 peer_addr,
62 &request.http_version,
63 &request.method,
64 &request.request_uri,
65 request_headers,
66 request.body.len(),
67
68 &response.status_code,
69 &response.reason_phrase,
70 response_headers,
71 response_body_parts_number,
72 response_body_length);
73
74 log_request_response
75 }
76
77 pub fn usage_information() -> String {
78 let mut log = "Usage:\n\n".to_string();
79 let command_line_arg_list = CommandLineArgument::get_command_line_arg_list();
80 for arg in command_line_arg_list {
81 let argument_info = format!(" {} environment variable\n -{} or --{} as command line line argument\n {}\n\n", arg.environment_variable, arg.short_form, arg.long_form, arg._hint.unwrap());
82 log = [log, argument_info].join("");
83 }
84 log = [log, "End of usage section\n\n".to_string()].join("");
85 log
86 }
87
88 pub fn info(name: &str) -> String {
89 let mut log = format!("{}\n", name).to_string();
90 const VERSION: &str = env!("CARGO_PKG_VERSION");
91 const AUTHORS: &str = env!("CARGO_PKG_AUTHORS");
92 const REPOSITORY: &str = env!("CARGO_PKG_REPOSITORY");
93 const DESCRIPTION: &str = env!("CARGO_PKG_DESCRIPTION");
94 const RUST_VERSION: &str = env!("CARGO_PKG_RUST_VERSION");
95 const LICENSE: &str = env!("CARGO_PKG_LICENSE");
96 let boxed_user = FileExt::get_current_user();
97 if boxed_user.is_err() {
98 let message = boxed_user.as_ref().err().unwrap();
99 eprintln!("{}", message)
100 }
101 let user: String = boxed_user.unwrap();
102
103 let boxed_working_directory = FileExt::get_static_filepath("");
104 if boxed_working_directory.is_err() {
105 let message = boxed_working_directory.as_ref().err().unwrap();
106 eprintln!("{}", message)
107 }
108
109 let working_directory: String = boxed_working_directory.unwrap();
110
111 let version = format!("Version: {}\n", VERSION);
112 log = [log, version].join("");
113
114 let authors = format!("Authors: {}\n", AUTHORS);
115 log = [log, authors].join("");
116
117 let repository = format!("Repository: {}\n", REPOSITORY);
118 log = [log, repository].join("");
119
120 let description = format!("Desciption: {}\n", DESCRIPTION);
121 log = [log, description].join("");
122
123 let rust_version = format!("Rust Version: {}\n", RUST_VERSION);
124 log = [log, rust_version].join("");
125
126 let license = format!("License: {}\n", LICENSE);
127 log = [log, license].join("");
128
129 let license = format!("User: {}\n", user);
130 log = [log, license].join("");
131
132 let license = format!("Working Directory: {}\n", working_directory);
133 log = [log, license].join("");
134
135 log
136 }
137
138 pub fn json(request: &Request, response: &Response, peer_addr: &SocketAddr) -> String {
143 use std::time::{SystemTime, UNIX_EPOCH};
144
145 let body_size: usize = response.content_range_list.iter()
146 .map(|cr| cr.body.len())
147 .sum();
148
149 let secs = SystemTime::now()
150 .duration_since(UNIX_EPOCH)
151 .unwrap_or_default()
152 .as_secs();
153
154 let timestamp = Log::format_iso8601(secs);
155
156 fn escape(s: &str) -> String {
157 s.replace('\\', "\\\\")
158 .replace('"', "\\\"")
159 .replace('\n', "\\n")
160 .replace('\r', "\\r")
161 }
162
163 format!(
164 "{{\"time\":\"{}\",\"remote_addr\":\"{}\",\"method\":\"{}\",\"path\":\"{}\",\"protocol\":\"{}\",\"status\":{},\"bytes\":{}}}",
165 timestamp,
166 peer_addr.ip(),
167 escape(&request.method),
168 escape(&request.request_uri),
169 escape(&request.http_version),
170 response.status_code,
171 body_size,
172 )
173 }
174
175 pub fn log_access(request: &Request, response: &Response, peer_addr: &SocketAddr) {
179 let format = std::env::var(crate::entry_point::Config::RWS_CONFIG_LOG_FORMAT)
180 .unwrap_or_default();
181 let line = if format == "json" {
182 Log::json(request, response, peer_addr)
183 } else {
184 Log::combined(request, response, peer_addr)
185 };
186 println!("{}", line);
187 }
188
189 fn format_iso8601(secs: u64) -> String {
190 let sec = secs % 60;
191 let min = (secs / 60) % 60;
192 let hour = (secs / 3600) % 24;
193 let days = secs / 86400;
194 let (year, month, day) = Log::days_to_ymd(days);
195 format!("{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z", year, month, day, hour, min, sec)
196 }
197
198 pub fn combined(request: &Request, response: &Response, peer_addr: &SocketAddr) -> String {
201 use std::time::{SystemTime, UNIX_EPOCH};
202
203 let body_size: usize = response.content_range_list.iter()
204 .map(|cr| cr.body.len())
205 .sum();
206
207 let secs = SystemTime::now()
208 .duration_since(UNIX_EPOCH)
209 .unwrap_or_default()
210 .as_secs();
211
212 let timestamp = Log::format_clf_timestamp(secs);
213
214 let body_str = if body_size > 0 { body_size.to_string() } else { "-".to_string() };
215
216 format!("{} - - [{}] \"{} {} {}\" {} {}",
217 peer_addr.ip(),
218 timestamp,
219 request.method,
220 request.request_uri,
221 request.http_version,
222 response.status_code,
223 body_str,
224 )
225 }
226
227 fn format_clf_timestamp(secs: u64) -> String {
228 let sec = secs % 60;
229 let min = (secs / 60) % 60;
230 let hour = (secs / 3600) % 24;
231 let days = secs / 86400;
232 let (year, month, day) = Log::days_to_ymd(days);
233 const MONTHS: [&str; 12] = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
234 format!("{:02}/{}/{:04}:{:02}:{:02}:{:02} +0000",
235 day, MONTHS[(month - 1) as usize], year, hour, min, sec)
236 }
237
238 fn days_to_ymd(days: u64) -> (u64, u64, u64) {
239 let z = days + 719468;
240 let era = z / 146097;
241 let doe = z % 146097;
242 let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
243 let y = yoe + era * 400;
244 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
245 let mp = (5 * doy + 2) / 153;
246 let d = doy - (153 * mp + 2) / 5 + 1;
247 let m = if mp < 10 { mp + 3 } else { mp - 9 };
248 let y = if m <= 2 { y + 1 } else { y };
249 (y, m, d)
250 }
251
252 pub fn server_url_thread_count(protocol: &str, bind_addr: &String, thread_count: i32) -> String {
253 let url = format!("Server is up and running at: {}://{}\n", protocol, &bind_addr);
254 let thread_count = format!("Spawned {} thread(s) to handle incoming requests\n", thread_count);
255 [url, thread_count].join("")
256 }
257}