Skip to main content

rws_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//! # extern crate rws_server as rust_web_server;
15//! use rust_web_server::middleware::{Middleware, WithMiddleware};
16//! use rust_web_server::application::Application;
17//! use rust_web_server::request::Request;
18//! use rust_web_server::response::Response;
19//! use rust_web_server::server::ConnectionInfo;
20//! use rust_web_server::app::App;
21//! use rust_web_server::core::New;
22//!
23//! pub struct LoggingMiddleware;
24//!
25//! impl Middleware for LoggingMiddleware {
26//!     fn handle(
27//!         &self,
28//!         request: &Request,
29//!         connection: &ConnectionInfo,
30//!         next: &dyn Application,
31//!     ) -> Result<Response, String> {
32//!         println!("{} {}", request.method, request.request_uri);
33//!         let response = next.execute(request, connection)?;
34//!         println!("  → {}", response.status_code);
35//!         Ok(response)
36//!     }
37//! }
38//!
39//! let app = WithMiddleware::new(App::new())
40//!     .wrap(LoggingMiddleware);
41//! ```
42
43#[cfg(test)]
44mod tests;
45
46use std::sync::Arc;
47
48use crate::application::Application;
49use crate::request::Request;
50use crate::response::Response;
51use crate::header::Header;
52use crate::server::ConnectionInfo;
53
54/// Built-in middleware that enforces the process-wide rate limit
55/// (configured via `RWS_CONFIG_RATE_LIMIT_MAX_REQUESTS` and
56/// `RWS_CONFIG_RATE_LIMIT_WINDOW_SECS`).
57///
58/// Returns `429 Too Many Requests` when the sliding-window budget for the
59/// client IP is exhausted; otherwise passes the request to the next layer.
60///
61/// # Example
62///
63/// ```rust,no_run
64/// # extern crate rws_server as rust_web_server;
65/// use rust_web_server::app::App;
66/// use rust_web_server::middleware::{WithMiddleware, RateLimitLayer};
67/// use rust_web_server::core::New;
68///
69/// let app = App::new().wrap(RateLimitLayer);
70/// ```
71pub struct RateLimitLayer;
72
73impl Middleware for RateLimitLayer {
74    /// Headers only: a streamed body passes through untouched.
75    fn accepts_streamed_body(&self, _request: &Request, _connection: &ConnectionInfo) -> bool {
76        true
77    }
78
79
80    fn handle(
81        &self,
82        request: &Request,
83        connection: &ConnectionInfo,
84        next: &dyn Application,
85    ) -> Result<Response, String> {
86        use crate::error::{AppError, IntoResponse};
87        let ip = crate::ip_filter::client_ip(request, &connection.client.ip);
88        if crate::rate_limit::global().check(&ip) {
89            next.execute(request, connection)
90        } else {
91            let mut response = AppError::TooManyRequests.into_response();
92            let retry_after = crate::rate_limit::global().retry_after(&ip);
93            response.headers.push(Header {
94                name: Header::_RETRY_AFTER.into(),
95                value: retry_after.to_string().into(),
96            });
97            Ok(response)
98        }
99    }
100
101    /// In-memory only: safe on the event loop.
102    fn runs_inline(&self, _request: &Request, _connection: &ConnectionInfo) -> bool {
103        true
104    }
105}
106
107/// A single middleware layer.
108///
109/// Receive the request, call `next.execute(request, connection)` to pass
110/// control to the next layer (or the inner application), and optionally
111/// inspect or transform the resulting response.
112///
113/// Short-circuit by returning a `Response` (or `Err`) without calling `next`.
114pub trait Middleware: Send + Sync {
115    fn handle(
116        &self,
117        request: &Request,
118        connection: &ConnectionInfo,
119        next: &dyn Application,
120    ) -> Result<Response, String>;
121
122    /// `true` when this layer's own work for `request` never blocks (in-memory
123    /// state, atomics, short mutexes) — see [`Application::runs_inline`]. A
124    /// [`WithMiddleware`] stack runs inline only if every layer and the inner
125    /// application agree. Default `false`.
126    fn runs_inline(&self, _request: &Request, _connection: &ConnectionInfo) -> bool {
127        false
128    }
129
130    /// `true` when `handle` would do nothing for `request` but call `next`
131    /// (e.g. a rate limit that isn't configured). A [`WithMiddleware`] stack
132    /// offers the inner application's `execute_async` only if every layer
133    /// passes the request through. Default `false`.
134    fn is_passthrough(&self, _request: &Request, _connection: &ConnectionInfo) -> bool {
135        false
136    }
137
138    /// `true` when this layer never reads `request.body`, so the inner
139    /// application may receive it as a stream ([`Application::streams_body`]).
140    /// A layer that inspects the body (CSRF tokens in a form, request
141    /// signatures, rewriting) must keep the default `false`: it would see an
142    /// empty body.
143    fn accepts_streamed_body(&self, _request: &Request, _connection: &ConnectionInfo) -> bool {
144        false
145    }
146
147    /// `true` when this layer answers `request` itself — it doesn't call
148    /// `next` — and reads its body as a stream (a proxy forwarding it as it
149    /// arrives). The body is then streamed whatever the layers after it and
150    /// the application would say. Requires `accepts_streamed_body`. Since
151    /// 18.3.0.
152    fn answers_streamed_body(&self, _request: &Request, _connection: &ConnectionInfo) -> bool {
153        false
154    }
155
156    /// A short name for this layer, as tools that describe an app list it
157    /// ([`Application::middleware_names`]): by default the type's name
158    /// without its module path or generic parameters (`CacheLayer`). Since
159    /// 18.28.0.
160    fn layer_name(&self) -> &'static str {
161        short_type_name(std::any::type_name::<Self>())
162    }
163}
164
165/// `path::to::Type<Generic>` → `Type`.
166pub(crate) fn short_type_name(full: &'static str) -> &'static str {
167    let base = full.split('<').next().unwrap_or(full);
168    base.rsplit("::").next().unwrap_or(base)
169}
170
171/// An [`Application`] that applies a stack of [`Middleware`] layers before
172/// dispatching to an inner application.
173///
174/// Layers are applied in registration order: the first `.wrap()`ed middleware
175/// runs first on the request path and last on the response path.
176pub struct WithMiddleware<A> {
177    inner: A,
178    /// Built once; shared by every clone (the servers clone the app per
179    /// request, so a `Vec` here was an allocation per request).
180    layers: Arc<[Arc<dyn Middleware>]>,
181}
182
183impl<A: Application> WithMiddleware<A> {
184    /// Wrap `app` with no initial middleware.
185    pub fn new(app: A) -> Self {
186        WithMiddleware { inner: app, layers: Arc::new([]) }
187    }
188
189    /// Add a middleware layer. Layers run in registration order.
190    pub fn wrap(mut self, layer: impl Middleware + 'static) -> Self {
191        let mut layers = self.layers.to_vec();
192        layers.push(Arc::new(layer));
193        self.layers = layers.into();
194        self
195    }
196}
197
198impl<A: Application + Send + Sync + Clone + 'static> WithMiddleware<A> {
199    /// Attach an MCP server that handles `POST /mcp`; all other requests fall
200    /// through to this middleware stack.
201    pub fn mcp(self, name: impl Into<String>, version: impl Into<String>) -> crate::mcp::McpServer {
202        crate::mcp::McpServer::new(name, version).wrap(self)
203    }
204}
205
206impl<A: Clone> Clone for WithMiddleware<A> {
207    fn clone(&self) -> Self {
208        WithMiddleware { inner: self.inner.clone(), layers: Arc::clone(&self.layers) }
209    }
210}
211
212impl<A: Application> Application for WithMiddleware<A> {
213    fn execute(&self, request: &Request, connection: &ConnectionInfo) -> Result<Response, String> {
214        Chain { app: &self.inner, layers: &self.layers, index: 0 }.execute(request, connection)
215    }
216
217    fn runs_inline(&self, request: &Request, connection: &ConnectionInfo) -> bool {
218        self.layers.iter().all(|l| l.runs_inline(request, connection)) && self.inner.runs_inline(request, connection)
219    }
220
221    #[cfg(feature = "http2")]
222    fn execute_async<'a>(&'a self, request: &'a Request, connection: &'a ConnectionInfo) -> Option<crate::application::AsyncResponse<'a>> {
223        if self.layers.iter().all(|l| l.is_passthrough(request, connection)) {
224            self.inner.execute_async(request, connection)
225        } else {
226            None
227        }
228    }
229    fn streams_body(&self, request: &Request, connection: &ConnectionInfo) -> bool {
230        for layer in self.layers.iter() {
231            if !layer.accepts_streamed_body(request, connection) {
232                return false;
233            }
234            if layer.answers_streamed_body(request, connection) {
235                return true;
236            }
237        }
238        self.inner.streams_body(request, connection)
239    }
240
241    fn registered_routes(&self) -> Vec<crate::router::RouteInfo> {
242        self.inner.registered_routes()
243    }
244
245    fn middleware_names(&self) -> Vec<&'static str> {
246        let mut names: Vec<&'static str> = self.layers.iter().map(|l| l.layer_name()).collect();
247        names.extend(self.inner.middleware_names());
248        names
249    }
250}
251
252// Internal chain that carries the remaining layers and the final app.
253struct Chain<'a> {
254    app: &'a dyn Application,
255    layers: &'a [Arc<dyn Middleware>],
256    index: usize,
257}
258
259impl<'a> Application for Chain<'a> {
260    fn execute(&self, request: &Request, connection: &ConnectionInfo) -> Result<Response, String> {
261        if self.index < self.layers.len() {
262            let next = Chain { app: self.app, layers: self.layers, index: self.index + 1 };
263            self.layers[self.index].handle(request, connection, &next)
264        } else {
265            self.app.execute(request, connection)
266        }
267    }
268}