Skip to main content

rust_web_server/middleware/
mod.rs

1//! Composable middleware pipeline.
2//!
3//! [`Middleware`] is a single layer that wraps request dispatch. Implement it
4//! to add cross-cutting behaviour — logging, authentication, rate limiting,
5//! header injection — without editing the inner application.
6//!
7//! [`WithMiddleware`] stacks one or more middleware layers around any
8//! [`Application`]. Layers run in registration order on the way in and in
9//! reverse order on the way out.
10//!
11//! # Example
12//!
13//! ```rust,no_run
14//! use rust_web_server::middleware::{Middleware, WithMiddleware};
15//! use rust_web_server::application::Application;
16//! use rust_web_server::request::Request;
17//! use rust_web_server::response::Response;
18//! use rust_web_server::server::ConnectionInfo;
19//! use rust_web_server::app::App;
20//! use rust_web_server::core::New;
21//!
22//! pub struct LoggingMiddleware;
23//!
24//! impl Middleware for LoggingMiddleware {
25//!     fn handle(
26//!         &self,
27//!         request: &Request,
28//!         connection: &ConnectionInfo,
29//!         next: &dyn Application,
30//!     ) -> Result<Response, String> {
31//!         println!("{} {}", request.method, request.request_uri);
32//!         let response = next.execute(request, connection)?;
33//!         println!("  → {}", response.status_code);
34//!         Ok(response)
35//!     }
36//! }
37//!
38//! let app = WithMiddleware::new(App::new())
39//!     .wrap(LoggingMiddleware);
40//! ```
41
42#[cfg(test)]
43mod tests;
44
45use std::sync::Arc;
46
47use crate::application::Application;
48use crate::request::Request;
49use crate::response::Response;
50use crate::header::Header;
51use crate::server::ConnectionInfo;
52
53/// Built-in middleware that enforces the process-wide rate limit
54/// (configured via `RWS_CONFIG_RATE_LIMIT_MAX_REQUESTS` and
55/// `RWS_CONFIG_RATE_LIMIT_WINDOW_SECS`).
56///
57/// Returns `429 Too Many Requests` when the sliding-window budget for the
58/// client IP is exhausted; otherwise passes the request to the next layer.
59///
60/// # Example
61///
62/// ```rust,no_run
63/// use rust_web_server::app::App;
64/// use rust_web_server::middleware::{WithMiddleware, RateLimitLayer};
65/// use rust_web_server::core::New;
66///
67/// let app = App::new().wrap(RateLimitLayer);
68/// ```
69pub struct RateLimitLayer;
70
71impl Middleware for RateLimitLayer {
72    fn handle(
73        &self,
74        request: &Request,
75        connection: &ConnectionInfo,
76        next: &dyn Application,
77    ) -> Result<Response, String> {
78        use crate::error::{AppError, IntoResponse};
79        if crate::rate_limit::global().check(&connection.client.ip) {
80            next.execute(request, connection)
81        } else {
82            let mut response = AppError::TooManyRequests.into_response();
83            let retry_after = crate::rate_limit::global().retry_after(&connection.client.ip);
84            response.headers.push(Header {
85                name: Header::_RETRY_AFTER.to_string(),
86                value: retry_after.to_string(),
87            });
88            Ok(response)
89        }
90    }
91
92    /// In-memory only: safe on the event loop.
93    fn runs_inline(&self, _request: &Request, _connection: &ConnectionInfo) -> bool {
94        true
95    }
96}
97
98/// A single middleware layer.
99///
100/// Receive the request, call `next.execute(request, connection)` to pass
101/// control to the next layer (or the inner application), and optionally
102/// inspect or transform the resulting response.
103///
104/// Short-circuit by returning a `Response` (or `Err`) without calling `next`.
105pub trait Middleware: Send + Sync {
106    fn handle(
107        &self,
108        request: &Request,
109        connection: &ConnectionInfo,
110        next: &dyn Application,
111    ) -> Result<Response, String>;
112
113    /// `true` when this layer's own work for `request` never blocks (in-memory
114    /// state, atomics, short mutexes) — see [`Application::runs_inline`]. A
115    /// [`WithMiddleware`] stack runs inline only if every layer and the inner
116    /// application agree. Default `false`.
117    fn runs_inline(&self, _request: &Request, _connection: &ConnectionInfo) -> bool {
118        false
119    }
120
121    /// `true` when `handle` would do nothing for `request` but call `next`
122    /// (e.g. a rate limit that isn't configured). A [`WithMiddleware`] stack
123    /// offers the inner application's `execute_async` only if every layer
124    /// passes the request through. Default `false`.
125    fn is_passthrough(&self, _request: &Request, _connection: &ConnectionInfo) -> bool {
126        false
127    }
128}
129
130/// An [`Application`] that applies a stack of [`Middleware`] layers before
131/// dispatching to an inner application.
132///
133/// Layers are applied in registration order: the first `.wrap()`ed middleware
134/// runs first on the request path and last on the response path.
135pub struct WithMiddleware<A> {
136    inner: A,
137    layers: Vec<Arc<dyn Middleware>>,
138}
139
140impl<A: Application> WithMiddleware<A> {
141    /// Wrap `app` with no initial middleware.
142    pub fn new(app: A) -> Self {
143        WithMiddleware { inner: app, layers: Vec::new() }
144    }
145
146    /// Add a middleware layer. Layers run in registration order.
147    pub fn wrap(mut self, layer: impl Middleware + 'static) -> Self {
148        self.layers.push(Arc::new(layer));
149        self
150    }
151}
152
153impl<A: Application + Send + Sync + Clone + 'static> WithMiddleware<A> {
154    /// Attach an MCP server that handles `POST /mcp`; all other requests fall
155    /// through to this middleware stack.
156    pub fn mcp(self, name: impl Into<String>, version: impl Into<String>) -> crate::mcp::McpServer {
157        crate::mcp::McpServer::new(name, version).wrap(self)
158    }
159}
160
161impl<A: Clone> Clone for WithMiddleware<A> {
162    fn clone(&self) -> Self {
163        WithMiddleware { inner: self.inner.clone(), layers: self.layers.clone() }
164    }
165}
166
167impl<A: Application> Application for WithMiddleware<A> {
168    fn execute(&self, request: &Request, connection: &ConnectionInfo) -> Result<Response, String> {
169        Chain { app: &self.inner, layers: &self.layers, index: 0 }.execute(request, connection)
170    }
171
172    fn runs_inline(&self, request: &Request, connection: &ConnectionInfo) -> bool {
173        self.layers.iter().all(|l| l.runs_inline(request, connection)) && self.inner.runs_inline(request, connection)
174    }
175
176    #[cfg(feature = "http2")]
177    fn execute_async<'a>(&'a self, request: &'a Request, connection: &'a ConnectionInfo) -> Option<crate::application::AsyncResponse<'a>> {
178        if self.layers.iter().all(|l| l.is_passthrough(request, connection)) {
179            self.inner.execute_async(request, connection)
180        } else {
181            None
182        }
183    }
184}
185
186// Internal chain that carries the remaining layers and the final app.
187struct Chain<'a> {
188    app: &'a dyn Application,
189    layers: &'a [Arc<dyn Middleware>],
190    index: usize,
191}
192
193impl<'a> Application for Chain<'a> {
194    fn execute(&self, request: &Request, connection: &ConnectionInfo) -> Result<Response, String> {
195        if self.index < self.layers.len() {
196            let next = Chain { app: self.app, layers: self.layers, index: self.index + 1 };
197            self.layers[self.index].handle(request, connection, &next)
198        } else {
199            self.app.execute(request, connection)
200        }
201    }
202}