3232use std:: collections:: HashMap ;
3333use std:: sync:: Arc ;
3434use std:: sync:: Weak ;
35+ use std:: time:: Duration ;
3536#[ cfg( feature = "plugins" ) ]
3637use std:: sync:: atomic:: AtomicBool ;
3738
@@ -105,6 +106,10 @@ pub struct Router {
105106 /// Signal arbiter for in-process event emission and handling.
106107 #[ cfg( feature = "signals" ) ]
107108 signals : SignalArbiter ,
109+ /// Default timeout for all routes.
110+ pub ( crate ) timeout : Option < Duration > ,
111+ /// Fallback handler executed when a request times out.
112+ timeout_fallback : Option < BoxHandler > ,
108113}
109114
110115impl Default for Router {
@@ -129,6 +134,8 @@ impl Router {
129134 plugins_initialized : AtomicBool :: new ( false ) ,
130135 #[ cfg( feature = "signals" ) ]
131136 signals : SignalArbiter :: new ( ) ,
137+ timeout : None ,
138+ timeout_fallback : None ,
132139 } ;
133140
134141 #[ cfg( feature = "signals" ) ]
@@ -274,6 +281,39 @@ impl Router {
274281 next. run ( req) . await
275282 }
276283
284+ /// Executes the middleware chain with an optional timeout.
285+ ///
286+ /// If a timeout is specified and exceeded, the timeout fallback handler
287+ /// is invoked or a default 408 Request Timeout response is returned.
288+ async fn run_with_timeout (
289+ & self ,
290+ req : Request ,
291+ next : Next ,
292+ timeout_duration : Option < Duration > ,
293+ ) -> Response {
294+ match timeout_duration {
295+ Some ( duration) => {
296+ match tokio:: time:: timeout ( duration, next. run ( req) ) . await {
297+ Ok ( response) => response,
298+ Err ( _elapsed) => self . handle_timeout ( ) . await ,
299+ }
300+ }
301+ None => next. run ( req) . await ,
302+ }
303+ }
304+
305+ /// Returns the timeout response using the fallback handler or a default 408.
306+ async fn handle_timeout ( & self ) -> Response {
307+ if let Some ( handler) = & self . timeout_fallback {
308+ handler. call ( Request :: default ( ) ) . await
309+ } else {
310+ http:: Response :: builder ( )
311+ . status ( StatusCode :: REQUEST_TIMEOUT )
312+ . body ( TakoBody :: empty ( ) )
313+ . expect ( "valid 408 response" )
314+ }
315+ }
316+
277317 /// Dispatches an incoming request to the appropriate route handler.
278318 pub async fn dispatch ( & self , mut req : Request ) -> Response {
279319 let method = req. method ( ) . clone ( ) ;
@@ -315,6 +355,9 @@ impl Router {
315355 endpoint : Arc :: new ( route. handler . clone ( ) ) ,
316356 } ;
317357
358+ // Determine effective timeout: route-level overrides router-level
359+ let effective_timeout = route. get_timeout ( ) . or ( self . timeout ) ;
360+
318361 #[ cfg( feature = "signals" ) ]
319362 {
320363 let method_str = method. to_string ( ) ;
@@ -331,7 +374,7 @@ impl Router {
331374 ) )
332375 . await ;
333376
334- let response = next . run ( req) . await ;
377+ let response = self . run_with_timeout ( req, next , effective_timeout ) . await ;
335378
336379 let mut done_meta: HashMap < String , String , BuildHasher > =
337380 HashMap :: with_hasher ( BuildHasher :: default ( ) ) ;
@@ -350,7 +393,7 @@ impl Router {
350393
351394 #[ cfg( not( feature = "signals" ) ) ]
352395 {
353- return next . run ( req) . await ;
396+ return self . run_with_timeout ( req, next , effective_timeout ) . await ;
354397 }
355398 }
356399
@@ -552,6 +595,54 @@ impl Router {
552595 self
553596 }
554597
598+ /// Sets a default timeout for all routes.
599+ ///
600+ /// This timeout can be overridden on individual routes using `Route::timeout`.
601+ /// When a request exceeds the timeout duration, the timeout fallback handler
602+ /// is invoked (if configured) or a 408 Request Timeout response is returned.
603+ ///
604+ /// # Examples
605+ ///
606+ /// ```rust
607+ /// use tako::router::Router;
608+ /// use std::time::Duration;
609+ ///
610+ /// let mut router = Router::new();
611+ /// router.timeout(Duration::from_secs(30));
612+ /// ```
613+ pub fn timeout ( & mut self , duration : Duration ) -> & mut Self {
614+ self . timeout = Some ( duration) ;
615+ self
616+ }
617+
618+ /// Sets a fallback handler that will be executed when a request times out.
619+ ///
620+ /// If no timeout fallback is set, a default 408 Request Timeout response is returned.
621+ ///
622+ /// # Examples
623+ ///
624+ /// ```rust
625+ /// use tako::{router::Router, responder::Responder, types::Request};
626+ /// use std::time::Duration;
627+ ///
628+ /// async fn timeout_handler(_req: Request) -> impl Responder {
629+ /// "Request took too long"
630+ /// }
631+ ///
632+ /// let mut router = Router::new();
633+ /// router.timeout(Duration::from_secs(30));
634+ /// router.timeout_fallback(timeout_handler);
635+ /// ```
636+ pub fn timeout_fallback < F , Fut , R > ( & mut self , handler : F ) -> & mut Self
637+ where
638+ F : Fn ( Request ) -> Fut + Clone + Send + Sync + ' static ,
639+ Fut : std:: future:: Future < Output = R > + Send + ' static ,
640+ R : Responder + Send + ' static ,
641+ {
642+ self . timeout_fallback = Some ( BoxHandler :: new :: < F , ( Request , ) > ( handler) ) ;
643+ self
644+ }
645+
555646 /// Registers a plugin with the router.
556647 ///
557648 /// Plugins extend the router's functionality by providing additional features
0 commit comments