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}