Skip to main content

rust_web_server/maintenance/
mod.rs

1//! Maintenance mode middleware.
2//!
3//! Set `MAINTENANCE_MODE` to `true` at runtime to return `503 Service
4//! Unavailable` for all requests except `/healthz` and `/readyz`. Clear it to
5//! resume normal traffic.
6//!
7//! # Example
8//!
9//! ```rust,no_run
10//! use rust_web_server::app::App;
11//! use rust_web_server::core::New;
12//! use rust_web_server::maintenance::{MAINTENANCE_MODE, MaintenanceLayer};
13//! use std::sync::atomic::Ordering;
14//!
15//! let app = App::new().wrap(MaintenanceLayer);
16//!
17//! // Enable maintenance mode at runtime.
18//! MAINTENANCE_MODE.store(true, Ordering::SeqCst);
19//! ```
20
21#[cfg(test)]
22mod tests;
23
24use std::sync::atomic::{AtomicBool, Ordering};
25
26use crate::application::Application;
27use crate::core::New as _;
28use crate::middleware::Middleware;
29use crate::request::Request;
30use crate::response::{Response, STATUS_CODE_REASON_PHRASE};
31use crate::range::Range;
32use crate::mime_type::MimeType;
33use crate::server::ConnectionInfo;
34
35/// Flip to `true` to activate maintenance mode; back to `false` to resume.
36pub static MAINTENANCE_MODE: AtomicBool = AtomicBool::new(false);
37
38fn service_unavailable() -> Response {
39    let mut r = Response::new();
40    r.status_code = *STATUS_CODE_REASON_PHRASE.n503_service_unavailable.status_code;
41    r.reason_phrase = STATUS_CODE_REASON_PHRASE.n503_service_unavailable.reason_phrase.to_string();
42    r.content_range_list = vec![Range::get_content_range(
43        b"Service Temporarily Unavailable".to_vec(),
44        MimeType::TEXT_PLAIN.to_string(),
45    )];
46    r
47}
48
49/// Middleware that returns `503` when [`MAINTENANCE_MODE`] is `true`.
50///
51/// Health probe paths (`/healthz`, `/readyz`) always pass through so that
52/// load balancers can still detect the pod is alive.
53pub struct MaintenanceLayer;
54
55impl Middleware for MaintenanceLayer {
56    fn handle(
57        &self,
58        request: &Request,
59        connection: &ConnectionInfo,
60        next: &dyn Application,
61    ) -> Result<Response, String> {
62        if MAINTENANCE_MODE.load(Ordering::Relaxed) {
63            let path = request.request_uri.split('?').next().unwrap_or(&request.request_uri);
64            if path != "/healthz" && path != "/readyz" {
65                return Ok(service_unavailable());
66            }
67        }
68        next.execute(request, connection)
69    }
70}