Skip to main content

ullun/
app.rs

1//! Main application orchestrator
2
3use crate::error::{Error, Result};
4use crate::handler::{BoxFuture, DynHandler, Handler, HandlerWithParams};
5use crate::middleware::{Layered, Middleware, Next};
6use crate::openapi::{self, OpenApiInfo, Operation, RouteKind};
7use crate::request::{Params, Request, StateMap};
8use crate::response::Response;
9use crate::router::Router;
10use crate::shutdown::{Context, Shutdown};
11use http_body_util::BodyExt;
12use hyper::server::conn::http1;
13use hyper::service::service_fn;
14use hyper_util::rt::TokioIo;
15use std::collections::HashMap;
16use std::convert::Infallible;
17use std::net::SocketAddr;
18use std::sync::Arc;
19use std::time::Duration;
20use tokio::net::TcpListener;
21
22/// Main application struct
23pub struct App {
24    router: Router,
25    middlewares: Vec<Arc<dyn Middleware>>,
26    max_body_size: Option<usize>, // in bytes
27    docs: HashMap<(String, String), Operation>,
28    openapi: Option<OpenApiInfo>,
29    state: StateMap,
30    background: Vec<BackgroundTask>,
31    shutdown_grace: Duration,
32    body_limits: Vec<(String, Option<usize>)>,
33    #[cfg(feature = "tls")]
34    tls: Option<crate::tls::TlsConfig>,
35    #[cfg(feature = "tls")]
36    tls_reload: Option<Duration>,
37}
38
39/// Facts about one connection that every request on it inherits
40struct ConnInfo {
41    peer: SocketAddr,
42    secure: bool,
43    client_cert: Option<Vec<u8>>,
44}
45
46type BackgroundTask = Box<dyn FnOnce(Context) -> BoxFuture<'static, ()> + Send>;
47
48/// Everything a running server shares between connections
49struct Shared {
50    router: Router,
51    middlewares: Vec<Arc<dyn Middleware>>,
52    max_body_size: Option<usize>,
53    body_limits: Vec<(String, Option<usize>)>,
54    state: Arc<StateMap>,
55    shutdown: Shutdown,
56}
57
58/// Route group builder
59pub struct RouteGroup {
60    prefix: String,
61    routes: Vec<(String, String, DynHandler)>, // (method, path, handler)
62    middlewares: Vec<Arc<dyn Middleware>>,
63    docs: Vec<(String, String, Operation)>, // (method, path relative to prefix, docs)
64    body_limit: Option<Option<usize>>,
65    /// Paths registered with `ws_with`, documented as WebSocket upgrades
66    ws_paths: Vec<String>,
67}
68
69impl App {
70    pub fn new() -> Self {
71        Self {
72            router: Router::new(),
73            middlewares: Vec::new(),
74            max_body_size: Some(10 * 1024 * 1024), // 10 MB default
75            docs: HashMap::new(),
76            openapi: None,
77            state: StateMap::default(),
78            background: Vec::new(),
79            shutdown_grace: Duration::from_secs(10),
80            body_limits: Vec::new(),
81            #[cfg(feature = "tls")]
82            tls: None,
83            #[cfg(feature = "tls")]
84            tls_reload: None,
85        }
86    }
87
88    /// Set maximum request body size in bytes (None = unlimited)
89    pub fn max_body_size(mut self, size: Option<usize>) -> Self {
90        self.max_body_size = size;
91        self
92    }
93
94    /// Different request body limit for everything under `prefix`
95    ///
96    /// `None` removes the limit. The longest matching prefix wins over the
97    /// default from [`App::max_body_size`], e.g. a small default with a large
98    /// limit for an upload route:
99    /// `App::new().max_body_size(Some(64 * 1024)).body_limit("/api/files", Some(25 << 20))`.
100    pub fn body_limit(mut self, prefix: &str, limit: Option<usize>) -> Self {
101        self.body_limits
102            .push((prefix.trim_end_matches('/').to_string(), limit));
103        self
104    }
105
106    /// Register shared application state, readable in handlers and
107    /// middleware with [`Request::state`]
108    ///
109    /// One value per type; registering the same type again replaces it. Use
110    /// interior mutability (`Mutex`, `RwLock`, atomics) for mutable state: the
111    /// value is shared by all requests and is not cloned.
112    ///
113    /// ```
114    /// use ullun::prelude::*;
115    /// use std::sync::atomic::{AtomicU64, Ordering};
116    ///
117    /// #[derive(Default)]
118    /// struct Hits(AtomicU64);
119    ///
120    /// async fn count(req: Request) -> Result<Response> {
121    ///     let hits = req.state::<Hits>()?.0.fetch_add(1, Ordering::Relaxed) + 1;
122    ///     Ok(Response::text(hits.to_string()))
123    /// }
124    ///
125    /// let app = App::new().state(Hits::default()).get("/", count);
126    /// ```
127    pub fn state<T: Send + Sync + 'static>(mut self, value: T) -> Self {
128        self.state.insert(value);
129        self
130    }
131
132    /// Run `task` alongside the server, started when it starts serving
133    ///
134    /// The task gets a [`Context`] with the shared state and the shutdown
135    /// signal; it should return when `ctx.sleep(..)` reports `false` or
136    /// `ctx.shutdown().wait()` completes. Finished tasks are awaited during
137    /// shutdown, for at most [`App::shutdown_grace`].
138    ///
139    /// ```
140    /// use std::time::Duration;
141    /// use ullun::prelude::*;
142    ///
143    /// struct Jobs;
144    ///
145    /// let app = App::new().state(Jobs).background(|ctx: Context| async move {
146    ///     while ctx.sleep(Duration::from_secs(60)).await {
147    ///         let _jobs = ctx.state::<Jobs>().unwrap();
148    ///         // purge expired sessions, refresh caches, ...
149    ///     }
150    /// });
151    /// ```
152    pub fn background<F, Fut>(mut self, task: F) -> Self
153    where
154        F: FnOnce(Context) -> Fut + Send + 'static,
155        Fut: std::future::Future<Output = ()> + Send + 'static,
156    {
157        self.background
158            .push(Box::new(move |ctx| Box::pin(task(ctx))));
159        self
160    }
161
162    /// Serve HTTPS instead of HTTP (feature `tls`), see [`crate::tls`]
163    #[cfg(feature = "tls")]
164    pub fn tls(mut self, config: crate::tls::TlsConfig) -> Self {
165        self.tls = Some(config);
166        self
167    }
168
169    /// Check the certificate files for changes this often and switch to renewed
170    /// certificates without a restart (feature `tls`; needs
171    /// `TlsConfig::from_pem_files`). A failed reload is logged and the previous
172    /// certificate stays in use.
173    #[cfg(feature = "tls")]
174    pub fn tls_reload_every(mut self, interval: Duration) -> Self {
175        self.tls_reload = Some(interval);
176        self
177    }
178
179    /// How long shutdown waits for in-flight requests and background tasks
180    /// (default 10 seconds)
181    pub fn shutdown_grace(mut self, grace: Duration) -> Self {
182        self.shutdown_grace = grace;
183        self
184    }
185
186    /// Create a route group with a common prefix
187    pub fn group(mut self, prefix: &str, configure: impl FnOnce(RouteGroup) -> RouteGroup) -> Self {
188        let group = RouteGroup::new(prefix);
189        let group = configure(group);
190
191        if let Some(limit) = group.body_limit {
192            self.body_limits.push((group.prefix.clone(), limit));
193        }
194        for (method, path, op) in group.docs {
195            let full_path = format!("{}{}", group.prefix, path);
196            self.docs.insert((method, full_path), op);
197        }
198
199        // Group middlewares wrap only this group's routes (inside the global ones)
200        let middlewares = group.middlewares;
201        for (method, path, handler) in group.routes {
202            let full_path = format!("{}{}", group.prefix, path);
203            let handler: DynHandler = if middlewares.is_empty() {
204                handler
205            } else {
206                Arc::new(Layered::new(middlewares.clone(), handler))
207            };
208            let kind = if method == "GET" && group.ws_paths.contains(&path) {
209                RouteKind::WebSocket
210            } else {
211                RouteKind::Http
212            };
213            if let Err(e) = self.router.insert_as(kind, &method, &full_path, handler) {
214                panic!("Failed to register {} route {}: {}", method, full_path, e);
215            }
216        }
217
218        self
219    }
220
221    /// Describe a registered route for the OpenAPI document
222    ///
223    /// `path` is the path exactly as registered, e.g. `/users/{id}`.
224    pub fn doc(mut self, method: &str, path: &str, operation: Operation) -> Self {
225        self.docs
226            .insert((method.to_ascii_uppercase(), path.to_string()), operation);
227        self
228    }
229
230    /// Publish an OpenAPI 3.1 document of all routes (and optionally Swagger UI)
231    ///
232    /// See the [`openapi`] module.
233    pub fn openapi(mut self, info: OpenApiInfo) -> Self {
234        self.openapi = Some(info);
235        self
236    }
237
238    /// Add GET route
239    pub fn get<H>(mut self, path: &str, handler: H) -> Self
240    where
241        H: Handler,
242    {
243        if let Err(e) = self
244            .router
245            .insert("GET", path, Arc::new(handler) as DynHandler)
246        {
247            panic!("Failed to register GET route {}: {}", path, e);
248        }
249        self
250    }
251
252    /// Add GET route with path params extractor
253    pub fn get_with_params<F, Fut>(mut self, path: &str, handler: F) -> Self
254    where
255        F: Fn(Params) -> Fut + Send + Sync + 'static,
256        Fut: std::future::Future<Output = Result<Response>> + Send + 'static,
257    {
258        if let Err(e) = self.router.insert(
259            "GET",
260            path,
261            Arc::new(HandlerWithParams::new(handler)) as DynHandler,
262        ) {
263            panic!("Failed to register GET route {}: {}", path, e);
264        }
265        self
266    }
267
268    /// Add POST route
269    pub fn post<H>(mut self, path: &str, handler: H) -> Self
270    where
271        H: Handler,
272    {
273        if let Err(e) = self
274            .router
275            .insert("POST", path, Arc::new(handler) as DynHandler)
276        {
277            panic!("Failed to register POST route {}: {}", path, e);
278        }
279        self
280    }
281
282    /// Add PUT route
283    pub fn put<H>(mut self, path: &str, handler: H) -> Self
284    where
285        H: Handler,
286    {
287        if let Err(e) = self
288            .router
289            .insert("PUT", path, Arc::new(handler) as DynHandler)
290        {
291            panic!("Failed to register PUT route {}: {}", path, e);
292        }
293        self
294    }
295
296    /// Add DELETE route
297    pub fn delete<H>(mut self, path: &str, handler: H) -> Self
298    where
299        H: Handler,
300    {
301        if let Err(e) = self
302            .router
303            .insert("DELETE", path, Arc::new(handler) as DynHandler)
304        {
305            panic!("Failed to register DELETE route {}: {}", path, e);
306        }
307        self
308    }
309
310    /// Add PATCH route
311    pub fn patch<H>(mut self, path: &str, handler: H) -> Self
312    where
313        H: Handler,
314    {
315        if let Err(e) = self
316            .router
317            .insert("PATCH", path, Arc::new(handler) as DynHandler)
318        {
319            panic!("Failed to register PATCH route {}: {}", path, e);
320        }
321        self
322    }
323
324    /// Accept WebSocket connections on `path` (feature `websocket`)
325    ///
326    /// The handler receives the original request (path params, query, headers,
327    /// extensions set by middleware) and the upgraded socket, and runs on its
328    /// own task until it returns.
329    #[cfg(feature = "websocket")]
330    pub fn ws<F, Fut>(self, path: &str, handler: F) -> Self
331    where
332        F: Fn(Request, crate::websocket::WebSocket) -> Fut + Send + Sync + 'static,
333        Fut: std::future::Future<Output = ()> + Send + 'static,
334    {
335        self.ws_with(path, crate::websocket::WsOptions::default(), handler)
336    }
337
338    /// Like [`App::ws`] with explicit size limit and allowed origins
339    #[cfg(feature = "websocket")]
340    pub fn ws_with<F, Fut>(
341        mut self,
342        path: &str,
343        options: crate::websocket::WsOptions,
344        handler: F,
345    ) -> Self
346    where
347        F: Fn(Request, crate::websocket::WebSocket) -> Fut + Send + Sync + 'static,
348        Fut: std::future::Future<Output = ()> + Send + 'static,
349    {
350        let route = crate::websocket::WsRoute::new(handler, options);
351        if let Err(e) = self.router.insert_as(
352            RouteKind::WebSocket,
353            "GET",
354            path,
355            Arc::new(route) as DynHandler,
356        ) {
357            panic!("Failed to register WebSocket route {}: {}", path, e);
358        }
359        self
360    }
361
362    /// Add middleware
363    pub fn middleware<M>(mut self, middleware: M) -> Self
364    where
365        M: Middleware,
366    {
367        self.middlewares.push(Arc::new(middleware));
368        self
369    }
370
371    /// Serve static files from a directory
372    pub fn serve_static(mut self, route_prefix: &str, dir_path: &str) -> Self {
373        use crate::static_files::StaticFileHandler;
374        let handler = StaticFileHandler::new(dir_path);
375        // matchit 0.9 catch-all syntax: {*param} matches the rest of the path
376        let prefix = route_prefix.trim_end_matches('/');
377        let handler: DynHandler = Arc::new(handler);
378        let route = format!("{}/{{*filepath}}", prefix);
379        if let Err(e) = self
380            .router
381            .insert_as(RouteKind::Hidden, "GET", &route, handler.clone())
382        {
383            panic!("Failed to register static file route {}: {}", route, e);
384        }
385        // The catch-all needs at least one segment, so also serve the prefix
386        // root (-> index.html). Skipped when the user already owns that route.
387        let root = if prefix.is_empty() { "/" } else { prefix };
388        let _ = self
389            .router
390            .insert_as(RouteKind::Hidden, "GET", root, handler.clone());
391        if !prefix.is_empty() {
392            let _ =
393                self.router
394                    .insert_as(RouteKind::Hidden, "GET", &format!("{}/", prefix), handler);
395        }
396        self
397    }
398
399    /// Run the server until CTRL+C or SIGTERM, then shut down gracefully
400    ///
401    /// Stops accepting connections, lets in-flight requests finish and waits
402    /// for background tasks, each for at most [`App::shutdown_grace`].
403    pub async fn run(self, addr: &str) -> Result<()> {
404        let addr: SocketAddr = addr
405            .parse()
406            .map_err(|e| Error::internal(format!("Invalid address: {}", e)))?;
407
408        let listener = TcpListener::bind(addr)
409            .await
410            .map_err(|e| Error::internal(format!("Failed to bind: {}", e)))?;
411
412        #[cfg(feature = "tls")]
413        let scheme = if self.tls.is_some() { "https" } else { "http" };
414        #[cfg(not(feature = "tls"))]
415        let scheme = "http";
416        println!("šŸš€ ullun-api listening on {}://{}", scheme, addr);
417
418        let result = self
419            .serve_with_shutdown(listener, async {
420                termination_signal().await;
421                println!("\nšŸ›‘ Shutting down gracefully...");
422            })
423            .await;
424        println!("āœ… Server stopped");
425        result
426    }
427
428    /// Serve on an already bound listener until the future is dropped.
429    ///
430    /// Useful for tests, benchmarks and embedding: bind to port `0`, read the
431    /// assigned port from `listener.local_addr()`, then call `serve`.
432    /// Unlike [`App::run`], this does not install signal handlers; use
433    /// [`App::serve_with_shutdown`] for a graceful stop.
434    pub async fn serve(self, listener: TcpListener) -> Result<()> {
435        self.serve_with_shutdown(listener, std::future::pending())
436            .await
437    }
438
439    /// Serve until `signal` completes, then shut down gracefully
440    ///
441    /// New connections are refused, idle keep-alive connections are closed,
442    /// running requests finish, the shutdown signal reaches
443    /// [`Request::shutdown_signal`] and background tasks, and the call returns
444    /// once everything has stopped or [`App::shutdown_grace`] has passed.
445    pub async fn serve_with_shutdown(
446        mut self,
447        listener: TcpListener,
448        signal: impl std::future::Future<Output = ()>,
449    ) -> Result<()> {
450        self = self.finalize();
451        let grace = self.shutdown_grace;
452        let background = std::mem::take(&mut self.background);
453        #[cfg(feature = "tls")]
454        let tls = self.tls.take();
455        #[cfg(feature = "tls")]
456        let tls_reload = self.tls_reload;
457        let (trigger, rx) = tokio::sync::watch::channel(false);
458        let shutdown = Shutdown::new(rx);
459        let shared = Arc::new(self.into_shared(shutdown.clone()));
460
461        #[allow(unused_mut)]
462        let mut tasks: Vec<_> = background
463            .into_iter()
464            .map(|task| {
465                let ctx = Context::new(shared.state.clone(), shutdown.clone());
466                tokio::spawn(task(ctx))
467            })
468            .collect();
469
470        #[cfg(feature = "tls")]
471        if let (Some(config), Some(every)) = (tls.clone(), tls_reload) {
472            let shutdown = shutdown.clone();
473            tasks.push(tokio::spawn(async move {
474                while shutdown.sleep(every).await {
475                    match config.reload() {
476                        Ok(true) => println!("šŸ” TLS certificate reloaded"),
477                        Ok(false) => {}
478                        Err(e) => eprintln!(
479                            "TLS certificate reload failed, keeping the previous one: {}",
480                            e.message()
481                        ),
482                    }
483                }
484            }));
485        }
486
487        // every connection holds a clone; the channel closes when all are done
488        let (done_tx, mut done_rx) = tokio::sync::mpsc::channel::<()>(1);
489        tokio::pin!(signal);
490        loop {
491            tokio::select! {
492                _ = &mut signal => break,
493                accepted = listener.accept() => match accepted {
494                    Ok((stream, peer)) => {
495                        #[cfg(feature = "tls")]
496                        if let Some(config) = tls.clone() {
497                            let (shared, shutdown, done) = (shared.clone(), shutdown.clone(), done_tx.clone());
498                            tokio::spawn(async move {
499                                // the handshake runs here, not in the accept loop: a slow
500                                // client must not hold up everybody else
501                                // scanners, plain HTTP on the TLS port and aborted handshakes just end here
502                                if let Ok((tls_stream, client_cert)) = config.accept(stream).await {
503                                    let info = ConnInfo { peer, secure: true, client_cert };
504                                    serve_connection(tls_stream, info, shared, shutdown, done).await;
505                                }
506                            });
507                            continue;
508                        }
509                        let info = ConnInfo { peer, secure: false, client_cert: None };
510                        tokio::spawn(serve_connection(
511                            stream,
512                            info,
513                            shared.clone(),
514                            shutdown.clone(),
515                            done_tx.clone(),
516                        ));
517                    }
518                    Err(e) => {
519                        // typically out of file descriptors: back off instead of dying
520                        eprintln!("Failed to accept connection: {}", e);
521                        tokio::time::sleep(Duration::from_millis(100)).await;
522                    }
523                },
524            }
525        }
526
527        let _ = trigger.send(true);
528        drop(done_tx);
529        drop(listener);
530        let drain = async {
531            let _ = done_rx.recv().await;
532            for task in tasks {
533                let _ = task.await;
534            }
535        };
536        if tokio::time::timeout(grace, drain).await.is_err() {
537            eprintln!(
538                "Shutdown grace period of {:?} elapsed, stopping anyway",
539                grace
540            );
541        }
542        Ok(())
543    }
544
545    fn into_shared(self, shutdown: Shutdown) -> Shared {
546        Shared {
547            router: self.router,
548            middlewares: self.middlewares,
549            max_body_size: self.max_body_size,
550            body_limits: self.body_limits,
551            state: Arc::new(self.state),
552            shutdown,
553        }
554    }
555
556    /// Last step before serving: publish the OpenAPI document, now that all
557    /// routes are known.
558    fn finalize(mut self) -> Self {
559        let Some(info) = self.openapi.take() else {
560            return self;
561        };
562        let spec = openapi::build(&info, self.router.registered(), &self.docs);
563        let spec = bytes::Bytes::from(serde_json::to_vec(&spec).unwrap_or_default());
564
565        let spec_handler = {
566            let spec = spec.clone();
567            move |_req: Request| {
568                let spec = spec.clone();
569                async move {
570                    Ok(Response::ok()
571                        .with_header("content-type", "application/json")
572                        .with_body(spec))
573                }
574            }
575        };
576        self.insert_hidden("GET", &info.spec_path, Arc::new(spec_handler));
577
578        if let Some(docs_path) = &info.docs_path {
579            let page = openapi::swagger_ui_html(&info.title, &info.spec_path);
580            let page_handler = move |_req: Request| {
581                let page = page.clone();
582                async move { Ok(Response::html(page)) }
583            };
584            self.insert_hidden("GET", docs_path, Arc::new(page_handler));
585        }
586        self
587    }
588
589    fn insert_hidden(&mut self, method: &str, path: &str, handler: DynHandler) {
590        if let Err(e) = self
591            .router
592            .insert_as(RouteKind::Hidden, method, path, handler)
593        {
594            panic!("Failed to register {} route {}: {}", method, path, e);
595        }
596    }
597}
598
599/// One connection, until it closes or shutdown drains it
600async fn serve_connection<S>(
601    stream: S,
602    info: ConnInfo,
603    shared: Arc<Shared>,
604    shutdown: Shutdown,
605    _done: tokio::sync::mpsc::Sender<()>,
606) where
607    S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static,
608{
609    let io = TokioIo::new(stream);
610    let info = Arc::new(info);
611    let service = service_fn(|req| {
612        let (shared, info) = (shared.clone(), info.clone());
613        async move { Ok::<_, Infallible>(handle_request(req, &info, shared).await) }
614    });
615    let conn = http1::Builder::new()
616        .serve_connection(io, service)
617        .with_upgrades();
618    tokio::pin!(conn);
619
620    let mut draining = false;
621    loop {
622        tokio::select! {
623            result = conn.as_mut() => {
624                if let Err(err) = result {
625                    eprintln!("Error serving connection: {:?}", err);
626                }
627                return;
628            }
629            _ = shutdown.wait(), if !draining => {
630                // idle connections close now, busy ones after their response
631                conn.as_mut().graceful_shutdown();
632                draining = true;
633            }
634        }
635    }
636}
637
638/// CTRL+C everywhere, plus SIGTERM on Unix (what container runtimes send)
639async fn termination_signal() {
640    #[cfg(unix)]
641    {
642        use tokio::signal::unix::{signal, SignalKind};
643        match signal(SignalKind::terminate()) {
644            Ok(mut term) => {
645                tokio::select! {
646                    _ = tokio::signal::ctrl_c() => {}
647                    _ = term.recv() => {}
648                }
649                return;
650            }
651            Err(e) => eprintln!("Cannot listen for SIGTERM: {}", e),
652        }
653    }
654    let _ = tokio::signal::ctrl_c().await;
655}
656
657impl Default for App {
658    fn default() -> Self {
659        Self::new()
660    }
661}
662
663impl RouteGroup {
664    fn new(prefix: &str) -> Self {
665        let prefix = prefix.trim_end_matches('/');
666        Self {
667            prefix: prefix.to_string(),
668            routes: Vec::new(),
669            middlewares: Vec::new(),
670            docs: Vec::new(),
671            body_limit: None,
672            ws_paths: Vec::new(),
673        }
674    }
675
676    /// Accept WebSocket connections on `path` below the group prefix; the
677    /// group's middleware (authentication, ticket checks, ...) runs before the
678    /// upgrade (feature `websocket`)
679    #[cfg(feature = "websocket")]
680    pub fn ws_with<F, Fut>(
681        mut self,
682        path: &str,
683        options: crate::websocket::WsOptions,
684        handler: F,
685    ) -> Self
686    where
687        F: Fn(Request, crate::websocket::WebSocket) -> Fut + Send + Sync + 'static,
688        Fut: std::future::Future<Output = ()> + Send + 'static,
689    {
690        let route = crate::websocket::WsRoute::new(handler, options);
691        self.routes.push((
692            "GET".to_string(),
693            path.to_string(),
694            Arc::new(route) as DynHandler,
695        ));
696        self.ws_paths.push(path.to_string());
697        self
698    }
699
700    /// Request body limit for this group's routes (`None` = unlimited),
701    /// overriding the application default
702    pub fn max_body_size(mut self, limit: Option<usize>) -> Self {
703        self.body_limit = Some(limit);
704        self
705    }
706
707    /// Describe a route of this group for the OpenAPI document; `path` is
708    /// relative to the group prefix, as given to `get`/`post`/...
709    pub fn doc(mut self, method: &str, path: &str, operation: Operation) -> Self {
710        self.docs
711            .push((method.to_ascii_uppercase(), path.to_string(), operation));
712        self
713    }
714
715    pub fn get<H>(mut self, path: &str, handler: H) -> Self
716    where
717        H: Handler,
718    {
719        self.routes.push((
720            "GET".to_string(),
721            path.to_string(),
722            Arc::new(handler) as DynHandler,
723        ));
724        self
725    }
726
727    pub fn post<H>(mut self, path: &str, handler: H) -> Self
728    where
729        H: Handler,
730    {
731        self.routes.push((
732            "POST".to_string(),
733            path.to_string(),
734            Arc::new(handler) as DynHandler,
735        ));
736        self
737    }
738
739    pub fn put<H>(mut self, path: &str, handler: H) -> Self
740    where
741        H: Handler,
742    {
743        self.routes.push((
744            "PUT".to_string(),
745            path.to_string(),
746            Arc::new(handler) as DynHandler,
747        ));
748        self
749    }
750
751    pub fn delete<H>(mut self, path: &str, handler: H) -> Self
752    where
753        H: Handler,
754    {
755        self.routes.push((
756            "DELETE".to_string(),
757            path.to_string(),
758            Arc::new(handler) as DynHandler,
759        ));
760        self
761    }
762
763    pub fn patch<H>(mut self, path: &str, handler: H) -> Self
764    where
765        H: Handler,
766    {
767        self.routes.push((
768            "PATCH".to_string(),
769            path.to_string(),
770            Arc::new(handler) as DynHandler,
771        ));
772        self
773    }
774
775    /// Add middleware that runs only for the routes of this group
776    ///
777    /// It runs after the application-wide middleware, in registration order,
778    /// regardless of whether it is added before or after the routes.
779    pub fn middleware<M>(mut self, middleware: M) -> Self
780    where
781        M: Middleware,
782    {
783        self.middlewares.push(Arc::new(middleware));
784        self
785    }
786}
787
788async fn handle_request(
789    req: hyper::Request<hyper::body::Incoming>,
790    conn: &ConnInfo,
791    shared: Arc<Shared>,
792) -> hyper::Response<crate::response::HyperBody> {
793    let Shared {
794        router,
795        middlewares,
796        max_body_size,
797        body_limits,
798        state,
799        shutdown,
800    } = &*shared;
801    let method = req.method().to_string();
802    let is_head = req.method() == http::Method::HEAD;
803    let path = req.uri().path().to_string();
804    let max_body_size = body_limit_for(body_limits, &path, *max_body_size);
805
806    // Convert hyper request to our Request type
807    let (parts, body) = req.into_parts();
808
809    // Enforce the body size limit while reading, so it also holds for chunked
810    // bodies without a Content-Length
811    let body_bytes = match max_body_size {
812        Some(max_size) => match read_limited(body, max_size).await {
813            Ok(bytes) => bytes,
814            Err(too_large) => return too_large.into_hyper(max_size),
815        },
816        None => match body.collect().await {
817            Ok(collected) => collected.to_bytes(),
818            Err(_) => bytes::Bytes::new(),
819        },
820    };
821
822    let mut request = Request::new(parts.method, parts.uri, parts.headers, body_bytes);
823    request.extensions = parts.extensions;
824    request.remote_addr = Some(conn.peer);
825    request.secure = conn.secure;
826    if let Some(cert) = &conn.client_cert {
827        request
828            .extensions
829            .insert(crate::request::ClientCertificate(cert.clone()));
830    }
831    request.state = Some(state.clone());
832    request.shutdown = Some(shutdown.clone());
833
834    // Match route. A miss still runs the middleware chain (CORS preflight,
835    // request ids, logging, security headers) and ends in 404 / 405 / OPTIONS.
836    let route = match router.match_route_with_pattern(&method, &path) {
837        Ok((handler, params, pattern)) => {
838            request.params = params;
839            request.matched_path = Some(pattern);
840            Route::Found(handler)
841        }
842        Err(_) => Route::Miss {
843            allow: router.allowed_methods(&path),
844        },
845    };
846
847    let result = if middlewares.is_empty() {
848        dispatch(route, request).await
849    } else {
850        // Route handler (or the miss) runs as the last link of the chain
851        let mut chain = middlewares.to_vec();
852        let final_handler = Arc::new(move |req: Request, _next: Next| {
853            let route = route.clone();
854            async move { dispatch(route, req).await }
855        });
856        chain.push(final_handler as Arc<dyn Middleware>);
857
858        Next::new(Arc::new(chain)).run(request).await
859    };
860
861    // Convert Result to Response
862    let response = match result {
863        Ok(resp) => resp,
864        Err(err) => Response::from_error(err),
865    };
866
867    // HEAD: same status and headers as GET, no body
868    let response = if is_head {
869        response.into_head()
870    } else {
871        response
872    };
873
874    response.into_hyper()
875}
876
877/// Most of a rejected request body that is read and thrown away before the 413
878/// is sent. Answering without reading makes the OS reset the connection on
879/// close, and clients (notably on Windows) then lose the response.
880const DRAIN_CAP: u64 = 8 * 1024 * 1024;
881const DRAIN_TIME: Duration = Duration::from_secs(3);
882
883struct BodyTooLarge;
884
885impl BodyTooLarge {
886    fn into_hyper(self, max_size: usize) -> hyper::Response<crate::response::HyperBody> {
887        Response::from_error(Error::custom(
888            413,
889            format!("Request body too large (max {} bytes)", max_size),
890        ))
891        // the connection is not reusable: unread body bytes may remain
892        .with_header("connection", "close")
893        .into_hyper()
894    }
895}
896
897/// Collect at most `max` bytes; on overflow discard (a bounded amount of) the rest
898async fn read_limited(
899    mut body: hyper::body::Incoming,
900    max: usize,
901) -> std::result::Result<bytes::Bytes, BodyTooLarge> {
902    use bytes::BufMut;
903    use hyper::body::Body;
904
905    // a declared length is rejected before a single byte is buffered
906    if body.size_hint().lower() > max as u64 {
907        drain(&mut body, 0).await;
908        return Err(BodyTooLarge);
909    }
910
911    let mut buf = bytes::BytesMut::new();
912    while let Some(frame) = body.frame().await {
913        let Ok(frame) = frame else {
914            return Ok(bytes::Bytes::new());
915        };
916        if let Ok(data) = frame.into_data() {
917            if buf.len() + data.len() > max {
918                drain(&mut body, (buf.len() + data.len()) as u64).await;
919                return Err(BodyTooLarge);
920            }
921            buf.put(data);
922        }
923    }
924    Ok(buf.freeze())
925}
926
927/// Read and drop up to [`DRAIN_CAP`] bytes (counting `already` read), for at most [`DRAIN_TIME`]
928async fn drain(body: &mut hyper::body::Incoming, already: u64) {
929    let _ = tokio::time::timeout(DRAIN_TIME, async {
930        let mut seen = already;
931        while seen < DRAIN_CAP {
932            match body.frame().await {
933                Some(Ok(frame)) => {
934                    if let Ok(data) = frame.into_data() {
935                        seen += data.len() as u64;
936                    }
937                }
938                _ => return,
939            }
940        }
941    })
942    .await;
943}
944
945/// Limit for `path`: the longest matching prefix rule, else the default
946fn body_limit_for(
947    rules: &[(String, Option<usize>)],
948    path: &str,
949    default: Option<usize>,
950) -> Option<usize> {
951    rules
952        .iter()
953        .filter(|(prefix, _)| {
954            prefix.is_empty()
955                || path == prefix
956                || path
957                    .strip_prefix(prefix.as_str())
958                    .is_some_and(|rest| rest.starts_with('/'))
959        })
960        .max_by_key(|(prefix, _)| prefix.len())
961        .map(|(_, limit)| *limit)
962        .unwrap_or(default)
963}
964
965/// Outcome of routing
966#[derive(Clone)]
967enum Route {
968    Found(DynHandler),
969    /// No handler for this method; `allow` is empty when the path is unknown
970    Miss {
971        allow: Vec<&'static str>,
972    },
973}
974
975async fn dispatch(route: Route, req: Request) -> Result<Response> {
976    match route {
977        Route::Found(handler) => handler.call(req).await,
978        Route::Miss { allow } if allow.is_empty() => Err(Error::not_found("Route not found")),
979        Route::Miss { allow } => {
980            let allow = allow.join(", ");
981            if req.method == http::Method::OPTIONS {
982                // automatic OPTIONS for known paths (CORS preflights are answered
983                // earlier, by the cors middleware)
984                Ok(Response::new(http::StatusCode::NO_CONTENT).with_header("allow", allow))
985            } else {
986                Ok(
987                    Response::from_error(Error::custom(405, "Method not allowed"))
988                        .with_header("allow", allow),
989                )
990            }
991        }
992    }
993}