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::{handler::RequestHandlerOpts, helpers, http_ext::MethodExt, Error, Result};
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!("<html><head><title>{status_code}</title></head><body><center><h1>{DEFAULT_BODY_CONTENT}</h1></center></body></html>")
76 };
77
78 let mut body = Body::empty();
79 let len = body_content.len() as u64;
80
81 if !method.is_head() {
82 body = Body::from(body_content)
83 }
84
85 let mut resp = Response::new(body);
86 *resp.status_mut() = *status_code;
87 resp.headers_mut()
88 .typed_insert(ContentType::from(mime::TEXT_HTML_UTF_8));
89 resp.headers_mut().typed_insert(ContentLength(len));
90 resp.headers_mut().typed_insert(AcceptRanges::bytes());
91
92 Ok(resp)
93}
94
95#[cfg(test)]
96mod tests {
97 use super::pre_process;
98 use crate::{handler::RequestHandlerOpts, Error};
99 use hyper::{Body, Request, Response, StatusCode};
100
101 fn make_request() -> Request<Body> {
102 Request::builder()
103 .method("GET")
104 .uri("/")
105 .body(Body::empty())
106 .unwrap()
107 }
108
109 fn get_status(result: Option<Result<Response<Body>, Error>>) -> Option<StatusCode> {
110 if let Some(Ok(response)) = result {
111 Some(response.status())
112 } else {
113 None
114 }
115 }
116
117 #[test]
118 fn test_maintenance_disabled() {
119 assert!(pre_process(
120 &RequestHandlerOpts {
121 maintenance_mode: false,
122 ..Default::default()
123 },
124 &make_request()
125 )
126 .is_none());
127 }
128
129 #[test]
130 fn test_maintenance_default() {
131 assert_eq!(
132 get_status(pre_process(
133 &RequestHandlerOpts {
134 maintenance_mode: true,
135 ..Default::default()
136 },
137 &make_request()
138 )),
139 Some(StatusCode::SERVICE_UNAVAILABLE)
140 );
141 }
142
143 #[test]
144 fn test_maintenance_custom_status() {
145 assert_eq!(
146 get_status(pre_process(
147 &RequestHandlerOpts {
148 maintenance_mode: true,
149 maintenance_mode_status: StatusCode::IM_A_TEAPOT,
150 ..Default::default()
151 },
152 &make_request()
153 )),
154 Some(StatusCode::IM_A_TEAPOT)
155 );
156 }
157}