Skip to main content

rws_server/async_state/
mod.rs

1//! Async-capable state-aware application — requires the `http2` feature (tokio).
2//!
3//! [`AsyncAppWithState<S>`] is the async counterpart to [`AppWithState<S>`]:
4//! handlers are `async fn` closures that can `await` database queries, HTTP
5//! clients, or any other async I/O without blocking the OS thread.
6//!
7//! Where a handler runs:
8//!
9//! - The async servers (HTTP/1.1, HTTP/2, HTTP/3) await it through
10//!   [`Application::execute_async`] when nothing synchronous wraps the app
11//!   (every middleware layer [`is_passthrough`]): on the connection's own
12//!   runtime when that's the shared `multi_thread` one, on the process-wide
13//!   `rws-async` runtime under per-core (`current_thread`) runtimes.
14//! - Otherwise — a middleware layer in front, the HTTP/1.1-only build,
15//!   [`TestClient::send`], any direct [`Application::execute`] call — the
16//!   calling thread waits while it runs on `rws-async`.
17//!
18//! Either way the runtime outlives the request, so tasks a handler leaves
19//! behind run: a `DbPool` gets its connection back (sqlx returns it from a
20//! spawned task) instead of closing it. Handlers must not block their
21//! thread — they share worker threads with other requests.
22//!
23//! Unmatched routes fall through to the built-in [`App`] controller chain,
24//! synchronously, without touching a runtime.
25//!
26//! [`is_passthrough`]: crate::middleware::Middleware::is_passthrough
27//! [`TestClient::send`]: crate::test_client::TestRequest::send
28//!
29//! # Example
30//!
31//! ```rust,no_run
32//! # extern crate rws_server as rust_web_server;
33//! use std::sync::Arc;
34//! use rust_web_server::async_state::AsyncAppWithState;
35//! use rust_web_server::core::New;
36//! use rust_web_server::response::{Response, STATUS_CODE_REASON_PHRASE};
37//! use rust_web_server::range::Range;
38//! use rust_web_server::mime_type::MimeType;
39//! use rust_web_server::router::PathParams;
40//! use rust_web_server::request::Request;
41//! use rust_web_server::server::ConnectionInfo;
42//!
43//! struct AppState {
44//!     greeting: String,
45//! }
46//!
47//! let app = AsyncAppWithState::new(AppState { greeting: "Hello".to_string() })
48//!     .get("/greet/:name", |_req, params, _conn, state| async move {
49//!         let name = params.get("name").unwrap_or("world");
50//!         let body = format!("{}, {}!", state.greeting, name);
51//!         let mut r = Response::new();
52//!         r.status_code = *STATUS_CODE_REASON_PHRASE.n200_ok.status_code;
53//!         r.reason_phrase = STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase.to_string();
54//!         r.content_range_list = vec![
55//!             Range::get_content_range(body.into_bytes(), MimeType::TEXT_PLAIN.to_string())
56//!         ];
57//!         r
58//!     });
59//! ```
60//!
61//! [`AppWithState<S>`]: crate::state::AppWithState
62
63#[cfg(test)]
64mod tests;
65
66use std::future::Future;
67use std::pin::Pin;
68use std::sync::Arc;
69
70use crate::app::App;
71use crate::application::Application;
72use crate::core::New;
73use crate::request::Request;
74use crate::response::Response;
75use crate::router::matcher::{self, Segment};
76use crate::router::PathParams;
77use crate::server::ConnectionInfo;
78#[cfg(feature = "openapi")]
79use crate::mime_type::MimeType;
80#[cfg(feature = "openapi")]
81use crate::range::Range;
82#[cfg(feature = "openapi")]
83use crate::response::STATUS_CODE_REASON_PHRASE;
84
85type BoxFuture<T> = Pin<Box<dyn Future<Output = T> + Send + 'static>>;
86
87type AsyncHandlerFn<S> = Arc<
88    dyn Fn(Request, PathParams, ConnectionInfo, Arc<S>) -> BoxFuture<Response> + Send + Sync,
89>;
90
91// ── AsyncRoute ────────────────────────────────────────────────────────────────
92
93#[derive(Clone)]
94struct AsyncRoute<S> {
95    method: String,
96    segments: Vec<Segment>,
97    handler: AsyncHandlerFn<S>,
98}
99
100// ── AsyncAppWithState ─────────────────────────────────────────────────────────
101
102/// An [`Application`] whose route handlers are `async` functions.
103///
104/// State is stored as `Arc<S>` and passed by value (cheap clone) to each
105/// handler invocation. Handlers receive owned `Request`, `PathParams`, and
106/// `ConnectionInfo` values so the returned future is `'static`.
107#[derive(Clone)]
108pub struct AsyncAppWithState<S> {
109    state: Arc<S>,
110    routes: Vec<AsyncRoute<S>>,
111    /// When `Some`, the fallback `App` is pinned to this config (see
112    /// [`App::with_config`]); when `None`, the fallback reads
113    /// `RWS_CONFIG_*` env vars per request via `App::new()`, same as `App`'s
114    /// own default.
115    config: Option<Arc<crate::server_config::ServerConfig>>,
116}
117
118impl<S: Send + Sync + 'static> AsyncAppWithState<S> {
119    /// Create a new `AsyncAppWithState` wrapping `state`.
120    pub fn new(state: S) -> Self {
121        AsyncAppWithState { state: Arc::new(state), routes: Vec::new(), config: None }
122    }
123
124    /// Pin the fallback [`App`] (used for any request this app's own routes
125    /// don't match) to an explicit [`crate::server_config::ServerConfig`],
126    /// instead of reading `RWS_CONFIG_*` environment variables per request.
127    ///
128    /// Mirrors [`App::with_config`] / [`crate::state::AppWithState::with_config`].
129    pub fn with_config(mut self, config: crate::server_config::ServerConfig) -> Self {
130        self.config = Some(Arc::new(config));
131        self
132    }
133
134    /// Return a reference to the shared state.
135    pub fn state(&self) -> &S {
136        &self.state
137    }
138
139    /// Return a snapshot of all registered routes as `(method, pattern)` pairs.
140    pub fn route_entries(&self) -> Vec<crate::router::RouteInfo> {
141        self.routes
142            .iter()
143            .map(|r| crate::router::RouteInfo {
144                method: r.method.clone(),
145                pattern: matcher::segments_to_pattern(&r.segments),
146            })
147            .collect()
148    }
149
150    /// The fallback `App` for requests this app's own routes don't match —
151    /// pinned to `self.config` if set, otherwise `App::new()`'s default
152    /// per-request env read.
153    fn fallback_app(&self) -> App {
154        match &self.config {
155            Some(c) => App::with_config((**c).clone()),
156            None => App::new(),
157        }
158    }
159
160    fn add<F, Fut>(mut self, method: &str, pattern: &str, handler: F) -> Self
161    where
162        F: Fn(Request, PathParams, ConnectionInfo, Arc<S>) -> Fut + Send + Sync + 'static,
163        Fut: Future<Output = Response> + Send + 'static,
164    {
165        self.routes.push(AsyncRoute {
166            method: method.to_string(),
167            segments: matcher::parse_pattern(pattern),
168            handler: Arc::new(move |req, params, conn, state| Box::pin(handler(req, params, conn, state))),
169        });
170        if let Some((last, earlier)) = self.routes.split_last() {
171            let earlier = earlier.iter().map(|r| (r.method.as_str(), r.segments.as_slice()));
172            if let Some(conflict) = crate::router::conflict_with(earlier, &last.method, &last.segments) {
173                conflict.warn_once();
174            }
175        }
176        self
177    }
178
179    /// Routes that never run because an earlier one with the same method
180    /// matches every path they do (18.28.0). See
181    /// [`Router::route_conflicts`](crate::router::Router::route_conflicts);
182    /// each is also printed once to stderr when it's registered.
183    pub fn route_conflicts(&self) -> Vec<crate::router::RouteConflict> {
184        crate::router::conflicts(self.routes.iter().map(|r| (r.method.as_str(), r.segments.as_slice())))
185    }
186
187    /// Register a typed handler (18.28.0): an `async fn` (or a sync
188    /// function) with extractor arguments annotated `#[get("/path")]` &
189    /// co.; the method and pattern come from the attribute.
190    ///
191    /// ```rust,ignore
192    /// #[get("/posts/{id}")]
193    /// async fn show_post(Path(id): Path<i64>, State(pool): State<DbPool>) -> Result<Json<Post>, AppError> {
194    ///     // … .await …
195    /// }
196    ///
197    /// let app = App::with_async_state(pool).route(show_post);
198    /// ```
199    pub fn route<H: crate::handler::AsyncHandler<S>>(self, handler: H) -> Self {
200        let handler = Arc::new(handler);
201        self.add(H::METHOD, H::PATTERN, move |request, params, connection, state| {
202            handler.handle_async(request, params, connection, state)
203        })
204    }
205
206    /// Register an async `GET` handler for `pattern`.
207    pub fn get<F, Fut>(self, pattern: &str, handler: F) -> Self
208    where
209        F: Fn(Request, PathParams, ConnectionInfo, Arc<S>) -> Fut + Send + Sync + 'static,
210        Fut: Future<Output = Response> + Send + 'static,
211    {
212        self.add("GET", pattern, handler)
213    }
214
215    /// Register an async `POST` handler for `pattern`.
216    pub fn post<F, Fut>(self, pattern: &str, handler: F) -> Self
217    where
218        F: Fn(Request, PathParams, ConnectionInfo, Arc<S>) -> Fut + Send + Sync + 'static,
219        Fut: Future<Output = Response> + Send + 'static,
220    {
221        self.add("POST", pattern, handler)
222    }
223
224    /// Register an async `PUT` handler for `pattern`.
225    pub fn put<F, Fut>(self, pattern: &str, handler: F) -> Self
226    where
227        F: Fn(Request, PathParams, ConnectionInfo, Arc<S>) -> Fut + Send + Sync + 'static,
228        Fut: Future<Output = Response> + Send + 'static,
229    {
230        self.add("PUT", pattern, handler)
231    }
232
233    /// Register an async `PATCH` handler for `pattern`.
234    pub fn patch<F, Fut>(self, pattern: &str, handler: F) -> Self
235    where
236        F: Fn(Request, PathParams, ConnectionInfo, Arc<S>) -> Fut + Send + Sync + 'static,
237        Fut: Future<Output = Response> + Send + 'static,
238    {
239        self.add("PATCH", pattern, handler)
240    }
241
242    /// Register an async `DELETE` handler for `pattern`.
243    pub fn delete<F, Fut>(self, pattern: &str, handler: F) -> Self
244    where
245        F: Fn(Request, PathParams, ConnectionInfo, Arc<S>) -> Fut + Send + Sync + 'static,
246        Fut: Future<Output = Response> + Send + 'static,
247    {
248        self.add("DELETE", pattern, handler)
249    }
250
251    /// Add `GET /openapi.json` (a generated OpenAPI 3.1 document covering
252    /// every route registered so far) and `GET /docs` (Swagger UI, loaded
253    /// from a CDN, pointed at `/openapi.json`).
254    ///
255    /// Call this *after* registering your routes — routes added afterward
256    /// still work but won't appear in the generated spec, since it's built
257    /// once at this call rather than read dynamically per request.
258    ///
259    /// Requires the `openapi` feature (and `http2`, since `AsyncAppWithState`
260    /// already requires it).
261    #[cfg(feature = "openapi")]
262    pub fn openapi(self, config: crate::openapi::OpenApiConfig) -> Self {
263        let spec_json = Arc::new(crate::openapi::build_spec(&config, &self.route_entries()));
264        let html = Arc::new(crate::openapi::swagger_ui_html("/openapi.json"));
265
266        let spec_for_route = Arc::clone(&spec_json);
267        self.get("/openapi.json", move |_req, _params, _conn, _state| {
268            let spec_for_route = Arc::clone(&spec_for_route);
269            async move {
270                let mut r = Response::new();
271                r.status_code = *STATUS_CODE_REASON_PHRASE.n200_ok.status_code;
272                r.reason_phrase = STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase.to_string();
273                r.content_range_list = vec![Range::get_content_range(
274                    spec_for_route.as_bytes().to_vec(),
275                    MimeType::APPLICATION_JSON.to_string(),
276                )];
277                r
278            }
279        })
280        .get("/docs", move |_req, _params, _conn, _state| {
281            let html = Arc::clone(&html);
282            async move {
283                let mut r = Response::new();
284                r.status_code = *STATUS_CODE_REASON_PHRASE.n200_ok.status_code;
285                r.reason_phrase = STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase.to_string();
286                r.content_range_list = vec![Range::get_content_range(
287                    html.as_bytes().to_vec(),
288                    MimeType::TEXT_HTML.to_string(),
289                )];
290                r
291            }
292        })
293    }
294
295    /// The matching route's handler future, or `None` when no route matches.
296    fn dispatch(&self, request: &Request, connection: &ConnectionInfo) -> Option<BoxFuture<Response>> {
297        let path = request.request_uri.split('?').next().unwrap_or(&request.request_uri);
298        let path_segs: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
299
300        for route in &self.routes {
301            if route.method != request.method {
302                continue;
303            }
304            if let Some(params_map) = matcher::try_match(&route.segments, &path_segs) {
305                let params = PathParams::from_map(params_map);
306                return Some((route.handler)(request.clone(), params, connection.clone(), Arc::clone(&self.state)));
307            }
308        }
309        None
310    }
311
312    /// Await the response on the caller's runtime (see the module docs for
313    /// where the handler runs); unmatched routes go to the fallback `App`.
314    pub(crate) async fn respond(&self, request: &Request, connection: &ConnectionInfo) -> Result<Response, String> {
315        match self.dispatch(request, connection) {
316            Some(handler) => Ok(crate::async_bridge::run_async(handler).await),
317            None => self.fallback_app().execute(request, connection),
318        }
319    }
320}
321
322impl<S: Send + Sync + 'static> Application for AsyncAppWithState<S> {
323    /// Runs a matching handler on the shared `rws-async` runtime and waits
324    /// for it; anything else goes to the fallback `App` on this thread.
325    fn execute(&self, request: &Request, connection: &ConnectionInfo) -> Result<Response, String> {
326        match self.dispatch(request, connection) {
327            Some(handler) => Ok(crate::async_bridge::block_on_shared(handler)),
328            None => self.fallback_app().execute(request, connection),
329        }
330    }
331
332    /// A matching route's handler, awaited by the async servers; `None` for
333    /// the fallback `App` (it's synchronous).
334    fn execute_async<'a>(&'a self, request: &'a Request, connection: &'a ConnectionInfo) -> Option<crate::application::AsyncResponse<'a>> {
335        let handler = self.dispatch(request, connection)?;
336        Some(Box::pin(async move { Ok(crate::async_bridge::run_async(handler).await) }))
337    }
338
339    fn registered_routes(&self) -> Vec<crate::router::RouteInfo> {
340        self.route_entries()
341    }
342}