Skip to main content

tachyon_web/routing/
handler.rs

1//! Handler trait and its implementations for async functions of various arities.
2//!
3//! This module provides the `Handler` trait, which is implemented automatically for
4//! `async fn`s with 0 to 16 extractors. The macro-generated impls are repetitive by
5//! necessity – Rust has no variadic generics yet – but are confined here to keep
6//! `routing/mod.rs` focused on routing logic.
7
8use hyper::{Request, Response};
9use std::future::Future;
10use std::pin::Pin;
11use std::sync::Arc;
12use std::task::{Context, Poll, Waker};
13
14use crate::http::response::{Body, IntoResponse};
15use crate::routing::extract::{FromRequest, FromRequestParts};
16
17/// A future that might be immediately ready, avoiding heap allocation.
18pub enum ResponseFuture {
19    /// The response was resolved immediately without any async waiting.
20    Ready(Option<Response<Body>>),
21    /// The response is pending and boxed.
22    Boxed(Pin<Box<dyn Future<Output = Response<Body>> + Send + 'static>>),
23}
24
25impl std::fmt::Debug for ResponseFuture {
26    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27        match self {
28            Self::Ready(res) => f.debug_tuple("Ready").field(res).finish(),
29            Self::Boxed(_) => f.debug_tuple("Boxed").field(&"<future>").finish(),
30        }
31    }
32}
33
34impl Future for ResponseFuture {
35    type Output = Response<Body>;
36
37    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
38        match &mut *self {
39            Self::Ready(res) => res.take().map_or_else(
40                || unreachable!("ResponseFuture polled after completion"),
41                Poll::Ready,
42            ),
43            Self::Boxed(fut) => fut.as_mut().poll(cx),
44        }
45    }
46}
47
48fn noop_waker() -> Waker {
49    Waker::noop().clone()
50}
51
52/// A pinned, boxed, `Send` future returning an HTTP response, or an immediately resolved response.
53pub type BoxedFuture = ResponseFuture;
54
55/// A type-erased handler: `Arc<dyn Fn(Request<Body>, Arc<S>) -> BoxedFuture>`.
56pub type BoxedHandler<S> =
57    Arc<dyn Fn(Request<Body>, Arc<S>) -> BoxedFuture + Send + Sync + 'static>;
58
59/// Marker for asynchronous handlers.
60#[derive(Debug)]
61pub struct AsyncHandler<T>(std::marker::PhantomData<T>);
62
63/// Marker for synchronous handlers.
64#[derive(Debug)]
65pub struct SyncHandler<T>(std::marker::PhantomData<T>);
66
67/// Trait for types that can handle an HTTP request.
68///
69/// This is blanket-implemented for both `async fn`s (which are marked with `AsyncHandler`) and
70/// synchronous `fn`/closures (which are marked with `SyncHandler`).
71///
72/// # Performance & Zero-Allocation Routing
73/// - **Arity-0 handlers** (no extractors) take the fast path: synchronous ones return
74///   `ResponseFuture::Ready` directly with zero box allocations; async ones are eagerly
75///   polled once (the same future instance is kept and reused if it turns out to be
76///   pending, never re-invoked, so any side effects before the first `.await` still run
77///   exactly once) and only fall back to a boxed future (`ResponseFuture::Boxed`) if they
78///   actually yield (i.e. genuinely await something).
79/// - **Handlers with ≥1 extractor** always go through `ResponseFuture::Boxed`, sync or async.
80///   This is because the last extractor implements [`FromRequest`], which is `async` (bodies
81///   may be streamed in rather than already buffered — see [`crate::routing::extract::BodyStream`]),
82///   so it can't be resolved before deciding `Ready` vs. `Boxed`.
83///
84/// # ⚠️ Thread Starvation & Blocking Warning
85/// Because Tachyon runs on a cooperative async thread pool (Tokio), blocking any worker thread
86/// with long-running synchronous code (e.g. `std::fs::read` or blocking database calls) will stall the event loop.
87/// - **DO**: Use sync handlers ONLY for instant CPU operations (e.g., formatting data, static templates, or simple state reads).
88/// - **DON'T**: Do heavy or blocking I/O synchronously inside sync handlers. Instead, use async versions or offload
89///   blocking calls to `tokio::task::spawn_blocking`.
90pub trait Handler<T, S>: Clone + Send + Sync + 'static {
91    /// Consume `self` and produce a future that resolves to the response.
92    fn call(self, req: Request<Body>, state: Arc<S>) -> BoxedFuture;
93}
94
95// ─── arity 0 ─────────────────────────────────────────────────────────────────
96
97// Async version
98impl<F, Fut, S, Res> Handler<AsyncHandler<()>, S> for F
99where
100    F: Fn() -> Fut + Clone + Send + Sync + 'static,
101    Fut: Future<Output = Res> + Send + 'static,
102    Res: IntoResponse + Send + 'static,
103    S: Send + Sync + 'static,
104{
105    fn call(self, _req: Request<Body>, _state: Arc<S>) -> BoxedFuture {
106        // Poll the *same* future instance that's returned as `Boxed` on `Pending` —
107        // re-invoking `self()` to get a "fresh" future (the previous approach) runs
108        // the handler body a second time from scratch, silently double-executing any
109        // side effects (logging, counters, mutex work, ...) that happen before the
110        // first await point. Pinning via `Box::pin` up front costs one allocation
111        // even on the immediately-ready path, but that's the price of only ever
112        // running the handler once.
113        let mut boxed: Pin<Box<dyn Future<Output = Res> + Send>> = Box::pin(self());
114        let waker = noop_waker();
115        let mut cx = Context::from_waker(&waker);
116
117        match boxed.as_mut().poll(&mut cx) {
118            Poll::Ready(res) => ResponseFuture::Ready(Some(res.into_response())),
119            Poll::Pending => {
120                ResponseFuture::Boxed(Box::pin(async move { boxed.await.into_response() }))
121            }
122        }
123    }
124}
125
126// Sync version
127impl<F, S, Res> Handler<SyncHandler<()>, S> for F
128where
129    F: Fn() -> Res + Clone + Send + Sync + 'static,
130    Res: IntoResponse + Send + 'static,
131    S: Send + Sync + 'static,
132{
133    fn call(self, _req: Request<Body>, _state: Arc<S>) -> BoxedFuture {
134        ResponseFuture::Ready(Some(self().into_response()))
135    }
136}
137
138// ─── arities 1-8 (macro-generated) ──────────────────────────────────────────
139
140macro_rules! impl_handler {
141    ( $($ty:ident),* ; $last:ident ) => {
142        // Async version
143        impl<F, Fut, S, Res, $($ty,)* $last> Handler<AsyncHandler<( $($ty,)* $last, )>, S> for F
144        where
145            F: Fn($($ty,)* $last) -> Fut + Clone + Send + Sync + 'static,
146            Fut: Future<Output = Res> + Send + 'static,
147            Res: IntoResponse + Send + 'static,
148            $( $ty: FromRequestParts<S> + Send + 'static, )*
149            $last: FromRequest<S> + Send + 'static,
150            S: Send + Sync + 'static,
151        {
152            #[allow(non_snake_case, unused_mut)]
153            fn call(self, req: Request<Body>, state: Arc<S>) -> BoxedFuture {
154                let (mut parts, body) = req.into_parts();
155                $(
156                    let $ty = match <$ty as FromRequestParts<S>>::from_request_parts(&mut parts, &*state) {
157                        Ok(v) => v,
158                        Err(r) => return ResponseFuture::Ready(Some(r.into_response())),
159                    };
160                )*
161                // The last extractor is `FromRequest`, which is `async` (it may need to
162                // await the body being streamed in) — so, unlike the parts extractors
163                // above, it can't be resolved before deciding Ready vs. Boxed. Every
164                // handler with at least one argument therefore goes through `Boxed`.
165                ResponseFuture::Boxed(Box::pin(async move {
166                    let req = Request::from_parts(parts, body);
167                    let $last = match <$last as FromRequest<S>>::from_request(req, &*state).await {
168                        Ok(v) => v,
169                        Err(r) => return r.into_response(),
170                    };
171                    self($($ty,)* $last).await.into_response()
172                }))
173            }
174        }
175
176        // Sync version
177        impl<F, S, Res, $($ty,)* $last> Handler<SyncHandler<( $($ty,)* $last, )>, S> for F
178        where
179            F: Fn($($ty,)* $last) -> Res + Clone + Send + Sync + 'static,
180            Res: IntoResponse + Send + 'static,
181            $( $ty: FromRequestParts<S> + Send + 'static, )*
182            $last: FromRequest<S> + Send + 'static,
183            S: Send + Sync + 'static,
184        {
185            #[allow(non_snake_case, unused_mut)]
186            fn call(self, req: Request<Body>, state: Arc<S>) -> BoxedFuture {
187                let (mut parts, body) = req.into_parts();
188                $(
189                    let $ty = match <$ty as FromRequestParts<S>>::from_request_parts(&mut parts, &*state) {
190                        Ok(v) => v,
191                        Err(r) => return ResponseFuture::Ready(Some(r.into_response())),
192                    };
193                )*
194                // See the async version above for why this can't stay on the `Ready` path.
195                ResponseFuture::Boxed(Box::pin(async move {
196                    let req = Request::from_parts(parts, body);
197                    let $last = match <$last as FromRequest<S>>::from_request(req, &*state).await {
198                        Ok(v) => v,
199                        Err(r) => return r.into_response(),
200                    };
201                    self($($ty,)* $last).into_response()
202                }))
203            }
204        }
205    };
206}
207
208impl_handler!(; A1);
209impl_handler!(A1; A2);
210impl_handler!(A1, A2; A3);
211impl_handler!(A1, A2, A3; A4);
212impl_handler!(A1, A2, A3, A4; A5);
213impl_handler!(A1, A2, A3, A4, A5; A6);
214impl_handler!(A1, A2, A3, A4, A5, A6; A7);
215impl_handler!(A1, A2, A3, A4, A5, A6, A7; A8);
216impl_handler!(A1, A2, A3, A4, A5, A6, A7, A8; A9);
217impl_handler!(A1, A2, A3, A4, A5, A6, A7, A8, A9; A10);
218impl_handler!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10; A11);
219impl_handler!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11; A12);
220impl_handler!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12; A13);
221impl_handler!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13; A14);
222impl_handler!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14; A15);
223impl_handler!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15; A16);
224
225#[cfg(test)]
226mod tests {
227    #![allow(clippy::unwrap_used)]
228    use super::*;
229
230    struct FailParts;
231    impl<S> FromRequestParts<S> for FailParts {
232        type Rejection = crate::http::error::Error;
233        fn from_request_parts(
234            _parts: &mut hyper::http::request::Parts,
235            _state: &S,
236        ) -> Result<Self, Self::Rejection> {
237            Err(crate::http::error::Error::Rejection {
238                status: hyper::StatusCode::BAD_REQUEST,
239                message: "parts fail".to_string(),
240            })
241        }
242    }
243
244    struct FailReq;
245    impl<S: Sync> FromRequest<S> for FailReq {
246        type Rejection = crate::http::error::Error;
247        async fn from_request(_req: Request<Body>, _state: &S) -> Result<Self, Self::Rejection> {
248            Err(crate::http::error::Error::Rejection {
249                status: hyper::StatusCode::BAD_REQUEST,
250                message: "req fail".to_string(),
251            })
252        }
253    }
254
255    struct SucceedParts;
256    impl<S> FromRequestParts<S> for SucceedParts {
257        type Rejection = crate::http::error::Error;
258        fn from_request_parts(
259            _parts: &mut hyper::http::request::Parts,
260            _state: &S,
261        ) -> Result<Self, Self::Rejection> {
262            Ok(Self)
263        }
264    }
265
266    struct SucceedReq;
267    impl<S: Sync> FromRequest<S> for SucceedReq {
268        type Rejection = crate::http::error::Error;
269        async fn from_request(_req: Request<Body>, _state: &S) -> Result<Self, Self::Rejection> {
270            Ok(Self)
271        }
272    }
273
274    #[tokio::test]
275    async fn test_handler_failures() {
276        async fn h1(_p: FailParts, _r: FailReq) -> &'static str {
277            "ok"
278        }
279
280        let req = Request::builder().body(Body::empty()).unwrap();
281        let fut = h1.call(req, Arc::new(()));
282        let res = fut.await;
283        assert_eq!(res.status(), hyper::StatusCode::BAD_REQUEST);
284    }
285
286    #[tokio::test]
287    async fn test_arity0_async_handler_runs_side_effects_exactly_once() {
288        use std::sync::atomic::{AtomicUsize, Ordering};
289        static CALLS: AtomicUsize = AtomicUsize::new(0);
290
291        async fn probe() -> &'static str {
292            CALLS.fetch_add(1, Ordering::SeqCst);
293            tokio::task::yield_now().await;
294            "ok"
295        }
296
297        let req = Request::builder().body(Body::empty()).unwrap();
298        let fut = probe.call(req, Arc::new(()));
299        let res = fut.await;
300        assert_eq!(res.status(), hyper::StatusCode::OK);
301        assert_eq!(
302            CALLS.load(Ordering::SeqCst),
303            1,
304            "handler body ran more than once for a single request"
305        );
306    }
307
308    #[test]
309    fn response_future_debug_does_not_panic() {
310        let ready = ResponseFuture::Ready(Some(Response::new(Body::empty())));
311        assert!(format!("{ready:?}").contains("Ready"));
312
313        let boxed = ResponseFuture::Boxed(Box::pin(async { Response::new(Body::empty()) }));
314        assert!(format!("{boxed:?}").contains("Boxed"));
315    }
316
317    /// A non-last extractor (`FromRequestParts`) that actually succeeds, paired with a
318    /// failing last extractor — covers the `Ok(v) => v` arm of the parts-extraction loop,
319    /// which every other test in this suite happens to only exercise via the `Err` arm.
320    #[tokio::test]
321    async fn test_handler_succeeds_on_parts_then_fails_on_last_extractor() {
322        async fn h(_p: SucceedParts, _r: FailReq) -> &'static str {
323            "unreachable"
324        }
325
326        let req = Request::builder().body(Body::empty()).unwrap();
327        let res = h.call(req, Arc::new(())).await;
328        assert_eq!(res.status(), hyper::StatusCode::BAD_REQUEST);
329    }
330
331    #[tokio::test]
332    async fn test_async_handler_with_all_extractors_succeeding() {
333        async fn h(_p: SucceedParts, _r: SucceedReq) -> &'static str {
334            "ok"
335        }
336
337        let req = Request::builder().body(Body::empty()).unwrap();
338        let res = h.call(req, Arc::new(())).await;
339        assert_eq!(res.status(), hyper::StatusCode::OK);
340    }
341
342    /// Sync-handler variant of `test_handler_failures` — the parts-extraction `Err` branch
343    /// of the *sync* `impl_handler!` arm is otherwise never exercised.
344    #[tokio::test]
345    async fn test_sync_handler_fails_on_parts_extractor() {
346        fn h(_p: FailParts, _r: SucceedReq) -> &'static str {
347            "unreachable"
348        }
349
350        let req = Request::builder().body(Body::empty()).unwrap();
351        let res = h.call(req, Arc::new(())).await;
352        assert_eq!(res.status(), hyper::StatusCode::BAD_REQUEST);
353    }
354
355    /// Sync-handler variant covering the *sync* `impl_handler!` arm's last-extractor `Err`
356    /// branch.
357    #[tokio::test]
358    async fn test_sync_handler_fails_on_last_extractor() {
359        fn h(_p: SucceedParts, _r: FailReq) -> &'static str {
360            "unreachable"
361        }
362
363        let req = Request::builder().body(Body::empty()).unwrap();
364        let res = h.call(req, Arc::new(())).await;
365        assert_eq!(res.status(), hyper::StatusCode::BAD_REQUEST);
366    }
367
368    #[tokio::test]
369    async fn test_sync_handler_with_all_extractors_succeeding() {
370        fn h(_p: SucceedParts, _r: SucceedReq) -> &'static str {
371            "ok"
372        }
373
374        let req = Request::builder().body(Body::empty()).unwrap();
375        let res = h.call(req, Arc::new(())).await;
376        assert_eq!(res.status(), hyper::StatusCode::OK);
377    }
378}