Skip to main content

ullun/
app.rs

1//! Main application orchestrator
2
3use crate::error::{Error, Result};
4use crate::handler::{DynHandler, Handler, HandlerWithParams};
5use crate::middleware::{Middleware, Next};
6use crate::request::{Params, Request};
7use crate::response::Response;
8use crate::router::Router;
9use http_body_util::BodyExt;
10use hyper::server::conn::http1;
11use hyper::service::service_fn;
12use hyper_util::rt::TokioIo;
13use std::convert::Infallible;
14use std::net::SocketAddr;
15use std::sync::Arc;
16use tokio::net::TcpListener;
17
18/// Main application struct
19pub struct App {
20    router: Router,
21    middlewares: Vec<Arc<dyn Middleware>>,
22    max_body_size: Option<usize>, // in bytes
23}
24
25/// Route group builder
26pub struct RouteGroup {
27    prefix: String,
28    routes: Vec<(String, String, DynHandler)>, // (method, path, handler)
29    middlewares: Vec<Arc<dyn Middleware>>,
30}
31
32impl App {
33    pub fn new() -> Self {
34        Self {
35            router: Router::new(),
36            middlewares: Vec::new(),
37            max_body_size: Some(10 * 1024 * 1024), // 10 MB default
38        }
39    }
40
41    /// Set maximum request body size in bytes (None = unlimited)
42    pub fn max_body_size(mut self, size: Option<usize>) -> Self {
43        self.max_body_size = size;
44        self
45    }
46
47    /// Create a route group with a common prefix
48    pub fn group(mut self, prefix: &str, configure: impl FnOnce(RouteGroup) -> RouteGroup) -> Self {
49        let group = RouteGroup::new(prefix);
50        let group = configure(group);
51
52        // Register all routes with prefix
53        for (method, path, handler) in group.routes {
54            let full_path = format!("{}{}", group.prefix, path);
55            if let Err(e) = self.router.insert(&method, &full_path, handler) {
56                panic!("Failed to register {} route {}: {}", method, full_path, e);
57            }
58        }
59
60        // Note: Group-level middlewares not yet supported - would apply globally
61        // Use route-specific middleware wrapping instead for now
62        if !group.middlewares.is_empty() {
63            eprintln!("Warning: RouteGroup.middleware() is not yet implemented. Middlewares will be ignored.");
64        }
65
66        self
67    }
68
69    /// Add GET route
70    pub fn get<H>(mut self, path: &str, handler: H) -> Self
71    where
72        H: Handler,
73    {
74        if let Err(e) = self
75            .router
76            .insert("GET", path, Arc::new(handler) as DynHandler)
77        {
78            panic!("Failed to register GET route {}: {}", path, e);
79        }
80        self
81    }
82
83    /// Add GET route with path params extractor
84    pub fn get_with_params<F, Fut>(mut self, path: &str, handler: F) -> Self
85    where
86        F: Fn(Params) -> Fut + Send + Sync + 'static,
87        Fut: std::future::Future<Output = Result<Response>> + Send + 'static,
88    {
89        if let Err(e) = self.router.insert(
90            "GET",
91            path,
92            Arc::new(HandlerWithParams::new(handler)) as DynHandler,
93        ) {
94            panic!("Failed to register GET route {}: {}", path, e);
95        }
96        self
97    }
98
99    /// Add POST route
100    pub fn post<H>(mut self, path: &str, handler: H) -> Self
101    where
102        H: Handler,
103    {
104        if let Err(e) = self
105            .router
106            .insert("POST", path, Arc::new(handler) as DynHandler)
107        {
108            panic!("Failed to register POST route {}: {}", path, e);
109        }
110        self
111    }
112
113    /// Add PUT route
114    pub fn put<H>(mut self, path: &str, handler: H) -> Self
115    where
116        H: Handler,
117    {
118        if let Err(e) = self
119            .router
120            .insert("PUT", path, Arc::new(handler) as DynHandler)
121        {
122            panic!("Failed to register PUT route {}: {}", path, e);
123        }
124        self
125    }
126
127    /// Add DELETE route
128    pub fn delete<H>(mut self, path: &str, handler: H) -> Self
129    where
130        H: Handler,
131    {
132        if let Err(e) = self
133            .router
134            .insert("DELETE", path, Arc::new(handler) as DynHandler)
135        {
136            panic!("Failed to register DELETE route {}: {}", path, e);
137        }
138        self
139    }
140
141    /// Add PATCH route
142    pub fn patch<H>(mut self, path: &str, handler: H) -> Self
143    where
144        H: Handler,
145    {
146        if let Err(e) = self
147            .router
148            .insert("PATCH", path, Arc::new(handler) as DynHandler)
149        {
150            panic!("Failed to register PATCH route {}: {}", path, e);
151        }
152        self
153    }
154
155    /// Add middleware
156    pub fn middleware<M>(mut self, middleware: M) -> Self
157    where
158        M: Middleware,
159    {
160        self.middlewares.push(Arc::new(middleware));
161        self
162    }
163
164    /// Serve static files from a directory
165    pub fn serve_static(mut self, route_prefix: &str, dir_path: &str) -> Self {
166        use crate::static_files::StaticFileHandler;
167        let handler = StaticFileHandler::new(dir_path);
168        // matchit 0.9 catch-all syntax: {*param} matches the rest of the path
169        let prefix = route_prefix.trim_end_matches('/');
170        let handler: DynHandler = Arc::new(handler);
171        let route = format!("{}/{{*filepath}}", prefix);
172        if let Err(e) = self.router.insert("GET", &route, handler.clone()) {
173            panic!("Failed to register static file route {}: {}", route, e);
174        }
175        // The catch-all needs at least one segment, so also serve the prefix
176        // root (-> index.html). Skipped when the user already owns that route.
177        let root = if prefix.is_empty() { "/" } else { prefix };
178        let _ = self.router.insert("GET", root, handler.clone());
179        if !prefix.is_empty() {
180            let _ = self.router.insert("GET", &format!("{}/", prefix), handler);
181        }
182        self
183    }
184
185    /// Run the server with graceful shutdown on CTRL+C
186    pub async fn run(self, addr: &str) -> Result<()> {
187        let addr: SocketAddr = addr
188            .parse()
189            .map_err(|e| Error::internal(format!("Invalid address: {}", e)))?;
190
191        let listener = TcpListener::bind(addr)
192            .await
193            .map_err(|e| Error::internal(format!("Failed to bind: {}", e)))?;
194
195        println!("šŸš€ ullun-api listening on http://{}", addr);
196
197        let router = Arc::new(self.router);
198        let middlewares = Arc::new(self.middlewares);
199        let max_body_size = self.max_body_size;
200
201        // Graceful shutdown signal
202        let shutdown = async {
203            tokio::signal::ctrl_c()
204                .await
205                .expect("Failed to install CTRL+C handler");
206            println!("\nšŸ›‘ Shutting down gracefully...");
207        };
208
209        tokio::select! {
210            _ = shutdown => {
211                println!("āœ… Server stopped");
212                Ok(())
213            }
214            result = Self::serve_loop(listener, router, middlewares, max_body_size) => result
215        }
216    }
217
218    /// Serve on an already bound listener until the future is dropped.
219    ///
220    /// Useful for tests, benchmarks and embedding: bind to port `0`, read the
221    /// assigned port from `listener.local_addr()`, then call `serve`.
222    /// Unlike [`App::run`], this does not install a CTRL+C handler.
223    pub async fn serve(self, listener: TcpListener) -> Result<()> {
224        Self::serve_loop(
225            listener,
226            Arc::new(self.router),
227            Arc::new(self.middlewares),
228            self.max_body_size,
229        )
230        .await
231    }
232
233    async fn serve_loop(
234        listener: TcpListener,
235        router: Arc<Router>,
236        middlewares: Arc<Vec<Arc<dyn Middleware>>>,
237        max_body_size: Option<usize>,
238    ) -> Result<()> {
239        loop {
240            let (stream, _) = listener
241                .accept()
242                .await
243                .map_err(|e| Error::internal(format!("Failed to accept: {}", e)))?;
244
245            let io = TokioIo::new(stream);
246            let router = router.clone();
247            let middlewares = middlewares.clone();
248
249            tokio::task::spawn(async move {
250                if let Err(err) = http1::Builder::new()
251                    .serve_connection(
252                        io,
253                        service_fn(|req| {
254                            let router = router.clone();
255                            let middlewares = middlewares.clone();
256                            async move {
257                                Ok::<_, Infallible>(
258                                    handle_request(req, router, middlewares, max_body_size).await,
259                                )
260                            }
261                        }),
262                    )
263                    .await
264                {
265                    eprintln!("Error serving connection: {:?}", err);
266                }
267            });
268        }
269    }
270}
271
272impl Default for App {
273    fn default() -> Self {
274        Self::new()
275    }
276}
277
278impl RouteGroup {
279    fn new(prefix: &str) -> Self {
280        let prefix = prefix.trim_end_matches('/');
281        Self {
282            prefix: prefix.to_string(),
283            routes: Vec::new(),
284            middlewares: Vec::new(),
285        }
286    }
287
288    pub fn get<H>(mut self, path: &str, handler: H) -> Self
289    where
290        H: Handler,
291    {
292        self.routes.push((
293            "GET".to_string(),
294            path.to_string(),
295            Arc::new(handler) as DynHandler,
296        ));
297        self
298    }
299
300    pub fn post<H>(mut self, path: &str, handler: H) -> Self
301    where
302        H: Handler,
303    {
304        self.routes.push((
305            "POST".to_string(),
306            path.to_string(),
307            Arc::new(handler) as DynHandler,
308        ));
309        self
310    }
311
312    pub fn put<H>(mut self, path: &str, handler: H) -> Self
313    where
314        H: Handler,
315    {
316        self.routes.push((
317            "PUT".to_string(),
318            path.to_string(),
319            Arc::new(handler) as DynHandler,
320        ));
321        self
322    }
323
324    pub fn delete<H>(mut self, path: &str, handler: H) -> Self
325    where
326        H: Handler,
327    {
328        self.routes.push((
329            "DELETE".to_string(),
330            path.to_string(),
331            Arc::new(handler) as DynHandler,
332        ));
333        self
334    }
335
336    pub fn patch<H>(mut self, path: &str, handler: H) -> Self
337    where
338        H: Handler,
339    {
340        self.routes.push((
341            "PATCH".to_string(),
342            path.to_string(),
343            Arc::new(handler) as DynHandler,
344        ));
345        self
346    }
347
348    pub fn middleware<M>(mut self, middleware: M) -> Self
349    where
350        M: Middleware,
351    {
352        self.middlewares.push(Arc::new(middleware));
353        self
354    }
355}
356
357async fn handle_request(
358    req: hyper::Request<hyper::body::Incoming>,
359    router: Arc<Router>,
360    middlewares: Arc<Vec<Arc<dyn Middleware>>>,
361    max_body_size: Option<usize>,
362) -> hyper::Response<http_body_util::Full<bytes::Bytes>> {
363    let method = req.method().to_string();
364    let path = req.uri().path().to_string();
365
366    // Convert hyper request to our Request type
367    let (parts, body) = req.into_parts();
368
369    // Enforce body size limit during streaming
370    let body_bytes = if let Some(max_size) = max_body_size {
371        // Check Content-Length header first (fast path)
372        if let Some(content_length) = parts.headers.get(http::header::CONTENT_LENGTH) {
373            if let Ok(length_str) = content_length.to_str() {
374                if let Ok(length) = length_str.parse::<usize>() {
375                    if length > max_size {
376                        let error_response = Response::from_error(Error::custom(
377                            413,
378                            format!("Request body too large (max {} bytes)", max_size),
379                        ));
380                        return error_response.into_hyper();
381                    }
382                }
383            }
384        }
385
386        // Limit actual stream reading (prevents bypass via chunked encoding)
387        let limited = http_body_util::Limited::new(body, max_size);
388        match limited.collect().await {
389            Ok(collected) => collected.to_bytes(),
390            Err(_) => {
391                // Body exceeded limit during reading
392                let error_response = Response::from_error(Error::custom(
393                    413,
394                    format!("Request body too large (max {} bytes)", max_size),
395                ));
396                return error_response.into_hyper();
397            }
398        }
399    } else {
400        match body.collect().await {
401            Ok(collected) => collected.to_bytes(),
402            Err(_) => bytes::Bytes::new(),
403        }
404    };
405
406    let mut request = Request::new(parts.method, parts.uri, parts.headers, body_bytes);
407
408    // Match route. A miss still runs the middleware chain (CORS preflight,
409    // request ids, logging, security headers) and ends in a 404.
410    let handler: Option<DynHandler> = match router.match_route(&method, &path) {
411        Ok((handler, params)) => {
412            request.params = params;
413            Some(handler)
414        }
415        Err(_) => None,
416    };
417
418    let result = if middlewares.is_empty() {
419        dispatch(handler, request).await
420    } else {
421        // Route handler (or the 404) runs as the last link of the chain
422        let mut chain = middlewares.to_vec();
423        let final_handler = Arc::new(move |req: Request, _next: Next| {
424            let handler = handler.clone();
425            async move { dispatch(handler, req).await }
426        });
427        chain.push(final_handler as Arc<dyn Middleware>);
428
429        Next::new(Arc::new(chain)).run(request).await
430    };
431
432    // Convert Result to Response
433    let response = match result {
434        Ok(resp) => resp,
435        Err(err) => Response::from_error(err),
436    };
437
438    response.into_hyper()
439}
440
441async fn dispatch(handler: Option<DynHandler>, req: Request) -> Result<Response> {
442    match handler {
443        Some(handler) => handler.call(req).await,
444        None => Err(Error::not_found("Route not found")),
445    }
446}