static_web_server/
maintenance_mode.rs1use headers::{AcceptRanges, ContentLength, ContentType, HeaderMapExt};
10use hyper::{Body, Method, Request, Response, StatusCode};
11use mime_guess::mime;
12use std::path::{Path, PathBuf};
13
14use crate::{Error, Result, handler::RequestHandlerOpts, helpers, http_ext::MethodExt};
15
16const DEFAULT_BODY_CONTENT: &str = "The server is in maintenance mode.";
17
18pub(crate) fn init(
20 maintenance_mode: bool,
21 maintenance_mode_status: StatusCode,
22 maintenance_mode_file: PathBuf,
23 handler_opts: &mut RequestHandlerOpts,
24) {
25 handler_opts.maintenance_mode = maintenance_mode;
26 handler_opts.maintenance_mode_status = maintenance_mode_status;
27 handler_opts.maintenance_mode_file = maintenance_mode_file;
28 tracing::info!(
29 "maintenance mode: enabled={}",
30 handler_opts.maintenance_mode
31 );
32 tracing::info!(
33 "maintenance mode status: {}",
34 handler_opts.maintenance_mode_status.as_str()
35 );
36 tracing::info!(
37 "maintenance mode file: \"{}\"",
38 handler_opts.maintenance_mode_file.display()
39 );
40}
41
42pub(crate) fn pre_process<T>(
44 opts: &RequestHandlerOpts,
45 req: &Request<T>,
46) -> Option<Result<Response<Body>, Error>> {
47 if opts.maintenance_mode {
48 Some(get_response(
49 req.method(),
50 &opts.maintenance_mode_status,
51 &opts.maintenance_mode_file,
52 ))
53 } else {
54 None
55 }
56}
57
58pub fn get_response(
60 method: &Method,
61 status_code: &StatusCode,
62 file_path: &Path,
63) -> Result<Response<Body>> {
64 tracing::debug!("server has entered into maintenance mode");
65 tracing::debug!("maintenance mode file path to use: {}", file_path.display());
66
67 let body_content = if file_path.is_file() {
68 String::from_utf8_lossy(&helpers::read_bytes_default(file_path))
69 .trim()
70 .to_owned()
71 } else {
72 tracing::debug!(
73 "maintenance mode file path not found or not a regular file, using a default message"
74 );
75 format!(
76 "<html><head><title>{status_code}</title></head><body><center><h1>{DEFAULT_BODY_CONTENT}</h1></center></body></html>"
77 )
78 };
79
80 let mut body = Body::empty();
81 let len = body_content.len() as u64;
82
83 if !method.is_head() {
84 body = Body::from(body_content)
85 }
86
87 let mut resp = Response::new(body);
88 *resp.status_mut() = *status_code;
89 resp.headers_mut()
90 .typed_insert(ContentType::from(mime::TEXT_HTML_UTF_8));
91 resp.headers_mut().typed_insert(ContentLength(len));
92 resp.headers_mut().typed_insert(AcceptRanges::bytes());
93
94 Ok(resp)
95}
96
97#[cfg(test)]
98mod tests {
99 use super::pre_process;
100 use crate::{Error, handler::RequestHandlerOpts};
101 use hyper::{Body, Request, Response, StatusCode};
102
103 fn make_request() -> Request<Body> {
104 Request::builder()
105 .method("GET")
106 .uri("/")
107 .body(Body::empty())
108 .unwrap()
109 }
110
111 fn get_status(result: Option<Result<Response<Body>, Error>>) -> Option<StatusCode> {
112 if let Some(Ok(response)) = result {
113 Some(response.status())
114 } else {
115 None
116 }
117 }
118
119 #[test]
120 fn test_maintenance_disabled() {
121 assert!(
122 pre_process(
123 &RequestHandlerOpts {
124 maintenance_mode: false,
125 ..Default::default()
126 },
127 &make_request()
128 )
129 .is_none()
130 );
131 }
132
133 #[test]
134 fn test_maintenance_default() {
135 assert_eq!(
136 get_status(pre_process(
137 &RequestHandlerOpts {
138 maintenance_mode: true,
139 ..Default::default()
140 },
141 &make_request()
142 )),
143 Some(StatusCode::SERVICE_UNAVAILABLE)
144 );
145 }
146
147 #[test]
148 fn test_maintenance_custom_status() {
149 assert_eq!(
150 get_status(pre_process(
151 &RequestHandlerOpts {
152 maintenance_mode: true,
153 maintenance_mode_status: StatusCode::IM_A_TEAPOT,
154 ..Default::default()
155 },
156 &make_request()
157 )),
158 Some(StatusCode::IM_A_TEAPOT)
159 );
160 }
161}