Skip to main content

rws_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//! # extern crate rws_server as rust_web_server;
11//! use rust_web_server::app::App;
12//! use rust_web_server::core::New;
13//! use rust_web_server::maintenance::{MAINTENANCE_MODE, MaintenanceLayer};
14//! use std::sync::atomic::Ordering;
15//!
16//! let app = App::new().wrap(MaintenanceLayer);
17//!
18//! // Enable maintenance mode at runtime.
19//! MAINTENANCE_MODE.store(true, Ordering::SeqCst);
20//! ```
21
22#[cfg(test)]
23mod tests;
24
25use std::sync::atomic::{AtomicBool, Ordering};
26
27use crate::application::Application;
28use crate::middleware::Middleware;
29use crate::request::Request;
30use crate::response::{Response, STATUS_CODE_REASON_PHRASE};
31use crate::server::ConnectionInfo;
32
33/// Flip to `true` to activate maintenance mode; back to `false` to resume.
34pub static MAINTENANCE_MODE: AtomicBool = AtomicBool::new(false);
35
36fn service_unavailable() -> Response {
37    crate::error::problem::problem_with_text(STATUS_CODE_REASON_PHRASE.n503_service_unavailable, "maintenance", "Service Temporarily Unavailable")
38}
39
40/// Middleware that returns `503` when [`MAINTENANCE_MODE`] is `true`.
41///
42/// Health probe paths (`/healthz`, `/readyz`) always pass through so that
43/// load balancers can still detect the pod is alive.
44pub struct MaintenanceLayer;
45
46impl Middleware for MaintenanceLayer {
47    /// Headers only: a streamed body passes through untouched.
48    fn accepts_streamed_body(&self, _request: &Request, _connection: &ConnectionInfo) -> bool {
49        true
50    }
51
52
53    fn handle(
54        &self,
55        request: &Request,
56        connection: &ConnectionInfo,
57        next: &dyn Application,
58    ) -> Result<Response, String> {
59        if MAINTENANCE_MODE.load(Ordering::Relaxed) {
60            let path = request.request_uri.split('?').next().unwrap_or(&request.request_uri);
61            if path != "/healthz" && path != "/readyz" {
62                return Ok(service_unavailable());
63            }
64        }
65        next.execute(request, connection)
66    }
67
68    /// In-memory only: safe on the event loop.
69    fn runs_inline(&self, _request: &Request, _connection: &ConnectionInfo) -> bool {
70        true
71    }
72
73    /// Off, it does nothing: config-driven proxy routes keep their
74    /// non-blocking forwarder (since 18.10.0, when the binary added this
75    /// layer to proxy mode for the admin API's maintenance switch).
76    fn is_passthrough(&self, _request: &Request, _connection: &ConnectionInfo) -> bool {
77        !MAINTENANCE_MODE.load(Ordering::Relaxed)
78    }
79}