ripress/app/
mod.rs

1//! # App Module
2//!
3//! The core application module for Ripress, providing Express.js-like functionality
4//! for building HTTP servers in Rust. This module contains the main [`App`] struct
5//! and [`Middleware`] definitions that form the foundation of a Ripress web application.
6//!
7//! ## Key Features
8//!
9//! - Express.js-like routing and middleware system
10//! - Built-in middleware for common tasks (CORS, logging, rate limiting, etc.)
11//! - Static file serving capabilities
12//! - WebSocket support (with `wynd` feature)
13//! - Async/await support throughout
14//!
15//! ## Basic Usage
16//!
17//! ```no_run
18//! use ripress::app::App;
19//! use ripress::types::RouterFns;
20//!
21//! #[tokio::main]
22//! async fn main() {
23//!     let mut app = App::new();
24//!
25//!     app.get("/", |_req, res| async move {
26//!         res.ok().text("Hello, World!")
27//!     });
28//!
29//!     app.listen(3000, || {
30//!         println!("Server running on http://localhost:3000");
31//!     }).await;
32//! }
33//! ```
34
35#![warn(missing_docs)]
36
37use crate::app::api_error::ApiError;
38
39use crate::helpers::{box_future_middleware, exec_post_middleware, exec_pre_middleware};
40#[cfg(feature = "with-wynd")]
41use crate::middlewares::WyndMiddleware;
42use crate::middlewares::body_limit::body_limit;
43#[cfg(feature = "compression")]
44use crate::middlewares::compression::{CompressionConfig, compression};
45use crate::middlewares::cors::{CorsConfig, cors};
46#[cfg(feature = "logger")]
47use crate::middlewares::logger::{LoggerConfig, logger};
48use crate::middlewares::rate_limiter::{RateLimiterConfig, rate_limiter};
49use crate::middlewares::shield::{ShieldConfig, shield};
50use crate::middlewares::{Middleware, MiddlewareType};
51use crate::req::HttpRequest;
52use crate::res::HttpResponse;
53use crate::router::Router;
54#[cfg(feature = "with-wynd")]
55use crate::types::WyndMiddlewareHandler;
56use crate::types::{HandlerMiddleware, HttpMethods, RouterFns, Routes};
57use bytes::Bytes;
58use http_body_util::{BodyExt, Full};
59use hyper::http::StatusCode;
60use hyper::server::conn::http1::Builder;
61use hyper::service::Service;
62use hyper::{Method, header};
63use hyper::{Request, Response};
64use hyper_staticfile::Static;
65use hyper_util::rt::TokioIo;
66use routerify_ng::RouterService;
67use routerify_ng::ext::RequestExt;
68use std::collections::HashMap;
69use std::net::SocketAddr;
70use std::path::Path;
71use std::sync::Arc;
72use tokio::net::TcpListener;
73
74pub(crate) mod api_error;
75
76/// The App struct is the core of Ripress, providing a simple interface for creating HTTP servers and handling requests.
77///
78/// It follows an Express-like pattern for route handling and middleware management. The App struct
79/// manages routes, middlewares, static file serving, and server lifecycle.
80///
81/// ## Features
82///
83/// - **Routing**: HTTP method-based routing (GET, POST, PUT, DELETE, etc.)
84/// - **Middleware**: Pre and post-processing middleware with path-based matching
85/// - **Static Files**: Serve static assets with proper headers and caching
86/// - **WebSocket Support**: Optional WebSocket support via the `wynd` crate
87/// - **Built-in Middleware**: CORS, logging, rate limiting, compression, and security headers
88///
89/// ## Example
90///
91/// ```ignore
92/// use ripress::app::App;
93/// use ripress::types::RouterFns;
94///
95/// #[tokio::main]
96/// async fn main() {
97///     let mut app = App::new();
98///
99///     // Add middleware
100///     app.use_cors(None);
101///     app.use_logger(None);
102///
103///     // Add routes
104///     app.get("/", |_req, res| async move {
105///         res.ok().text("Hello, World!")
106///     });
107///
108///     app.post("/api/users", |req, res| async move {
109///         // Handle user creation
110///         res.ok().json("User created")
111///     });
112///
113///     // Serve static files
114///     app.static_files("/public", "./public").unwrap();
115///
116///     // Start server
117///     app.listen(3000, || {
118///         println!("Server running on http://localhost:3000");
119///     }).await;
120/// }
121/// ```
122pub struct App {
123    /// The collection of registered routes organized by path and HTTP method.
124    routes: Routes,
125    /// The list of middleware functions to be applied to requests.
126    pub(crate) middlewares: Vec<Middleware>,
127    /// Static file mappings from mount path to filesystem path.
128    pub(crate) static_files: HashMap<&'static str, &'static str>,
129
130    pub(crate) graceful_shutdown: bool,
131    #[cfg(feature = "with-wynd")]
132    /// Optional WebSocket middleware (only available with `wynd` feature).
133    pub(crate) wynd_middleware: Option<WyndMiddleware>,
134}
135
136impl RouterFns for App {
137    fn routes(&mut self) -> &mut Routes {
138        &mut self.routes
139    }
140}
141
142impl App {
143    /// Creates a new App instance with empty routes and middleware.
144    ///
145    /// This is the starting point for building a Ripress application. The returned
146    /// App instance has no routes or middleware configured and is ready to be customized.
147    ///
148    /// ## Example
149    ///
150    /// ```
151    /// use ripress::app::App;
152    ///
153    /// let mut app = App::new();
154    /// ```
155    pub fn new() -> Self {
156        App {
157            routes: HashMap::new(),
158            middlewares: Vec::new(),
159            static_files: HashMap::new(),
160            graceful_shutdown: false,
161            #[cfg(feature = "with-wynd")]
162            wynd_middleware: None,
163        }
164    }
165
166    /// Adds a middleware to the application (deprecated).
167    ///
168    /// ## Deprecation Notice
169    ///
170    /// This method is deprecated since version 1.9.0. Use [`use_pre_middleware`] instead
171    /// for better clarity about middleware execution order.
172    ///
173    /// ## Arguments
174    ///
175    /// * `path` - Optional path prefix where the middleware should apply. Defaults to "/" (all paths)
176    /// * `middleware` - The middleware function to add
177    ///
178    /// ## Example
179    ///
180    /// ```
181    /// use ripress::app::App;
182    ///
183    /// let mut app = App::new();
184    ///
185    /// // This is deprecated - use use_pre_middleware instead
186    /// app.use_middleware(Some("/api"), |req, res| async move {
187    ///     println!("Processing API request");
188    ///     (req, None)
189    /// });
190    /// ```
191    #[deprecated(since = "1.9.0", note = "Use `use_pre_middleware` instead")]
192    pub fn use_middleware<F, Fut, P>(&mut self, path: P, middleware: F) -> &mut Self
193    where
194        P: Into<Option<&'static str>>,
195        F: Fn(HttpRequest, HttpResponse) -> Fut + Send + Sync + 'static,
196        Fut: std::future::Future<Output = (HttpRequest, Option<HttpResponse>)> + Send + 'static,
197    {
198        let path = path.into().unwrap_or("/").to_string();
199        self.middlewares.push(Middleware {
200            func: Self::middleware_from_closure(middleware),
201            path,
202            middleware_type: MiddlewareType::Pre,
203        });
204        self
205    }
206
207    /// Enables graceful shutdown for the application.
208    ///
209    /// When graceful shutdown is enabled, the server will listen for a shutdown signal
210    /// (such as Ctrl+C) and attempt to shut down cleanly, finishing any in-flight requests
211    /// before exiting. This is useful for production environments where you want to avoid
212    /// abruptly terminating active connections.
213    ///
214    /// ## Example
215    ///
216    /// ```
217    /// use ripress::app::App;
218    ///
219    /// let mut app = App::new();
220    /// app.with_graceful_shutdown();
221    /// ```
222    pub fn with_graceful_shutdown(&mut self) {
223        self.graceful_shutdown = true
224    }
225
226    /// Adds a pre-execution middleware to the application.
227    ///
228    /// Pre-middlewares are executed before the route handler. They can modify the request,
229    /// short-circuit the processing by returning a response, or pass control to the next
230    /// middleware in the chain.
231    ///
232    /// ## Arguments
233    ///
234    /// * `path` - Optional path prefix where the middleware should apply. If `None`, defaults to "/" (all paths)
235    /// * `middleware` - The middleware function that receives `(HttpRequest, HttpResponse)` and returns a future
236    ///   resolving to `(HttpRequest, Option<HttpResponse>)`. If `Some(response)` is returned, processing stops
237    ///   and the response is sent. If `None` is returned, processing continues.
238    ///
239    /// ## Example
240    ///
241    /// ```
242    /// use ripress::app::App;
243    ///
244    /// let mut app = App::new();
245    ///
246    /// // Authentication middleware for API routes
247    /// app.use_pre_middleware(Some("/api"), |req, res| async move {
248    ///     if req.headers.get("authorization").is_none() {
249    ///         return (req, Some(res.unauthorized().text("Missing authorization header")));
250    ///     }
251    ///     (req, None) // Continue processing
252    /// });
253    ///
254    /// // Logging middleware for all routes
255    /// app.use_pre_middleware(None, |req, res| async move {
256    ///     println!("Request: {} {}", req.method, req.path);
257    ///     (req, None)
258    /// });
259    /// ```
260    pub fn use_pre_middleware<F, Fut, P>(&mut self, path: P, middleware: F) -> &mut Self
261    where
262        P: Into<Option<&'static str>>,
263        F: Fn(HttpRequest, HttpResponse) -> Fut + Send + Sync + 'static,
264        Fut: std::future::Future<Output = (HttpRequest, Option<HttpResponse>)> + Send + 'static,
265    {
266        let path = path.into().unwrap_or("/").to_string();
267        self.middlewares.push(Middleware {
268            func: Self::middleware_from_closure(middleware),
269            path: path,
270            middleware_type: MiddlewareType::Pre,
271        });
272        self
273    }
274
275    /// Adds a post-execution middleware to the application.
276    ///
277    /// Post-middlewares are executed after the route handler has processed the request.
278    /// They can modify the response or perform cleanup operations. They cannot short-circuit
279    /// processing since the route handler has already run.
280    ///
281    /// ## Arguments
282    ///
283    /// * `path` - Optional path prefix where the middleware should apply. If `None`, defaults to "/" (all paths)
284    /// * `middleware` - The middleware function that receives `(HttpRequest, HttpResponse)` where the response
285    ///   has been populated by the route handler. Returns a future resolving to `(HttpRequest, Option<HttpResponse>)`.
286    ///
287    /// ## Example
288    ///
289    /// ```
290    /// use ripress::app::App;
291    ///
292    /// let mut app = App::new();
293    ///
294    /// // Add security headers to all responses
295    /// app.use_post_middleware(None, |req, mut res| async move {
296    ///     res = res.set_header("X-Frame-Options", "DENY")
297    ///         .set_header("X-Content-Type-Options", "nosniff");
298    ///     (req, Some(res))
299    /// });
300    ///
301    /// // Log response status for API routes
302    /// app.use_post_middleware(Some("/api"), |req, res| async move {
303    ///     println!("API Response: {}", req.path);
304    ///     (req, Some(res))
305    /// });
306    /// ```
307    pub fn use_post_middleware<F, Fut, P>(&mut self, path: P, middleware: F) -> &mut Self
308    where
309        P: Into<Option<&'static str>>,
310        F: Fn(HttpRequest, HttpResponse) -> Fut + Send + Sync + 'static,
311        Fut: std::future::Future<Output = (HttpRequest, Option<HttpResponse>)> + Send + 'static,
312    {
313        let path = path.into().unwrap_or("/").to_string();
314        self.middlewares.push(Middleware {
315            func: Self::middleware_from_closure(middleware),
316            path: path,
317            middleware_type: MiddlewareType::Post,
318        });
319        self
320    }
321
322    /// Adds a logger middleware to the application.
323    ///
324    /// The logger middleware logs incoming HTTP requests with configurable options.
325    /// It uses the `tracing` crate for logging, so make sure to initialize a tracing
326    /// subscriber in your application.
327    ///
328    /// ## Arguments
329    ///
330    /// * `config` - Optional [`LoggerConfig`] to customize logging behavior. If `None`,
331    ///   default settings are used which log basic request information.
332    ///
333    /// ## Example
334    ///
335    /// ```
336    /// use ripress::app::App;
337    /// use ripress::middlewares::logger::LoggerConfig;
338    ///
339    /// // Initialize tracing (required for logging to work)
340    /// tracing_subscriber::fmt::init();
341    ///
342    /// let mut app = App::new();
343    ///
344    /// // Use default logger settings
345    /// app.use_logger(None);
346    ///
347    /// // Use custom logger configuration
348    /// app.use_logger(Some(LoggerConfig {
349    ///     method: true,      // Log HTTP method
350    ///     path: true,        // Log request path
351    ///     status: true,      // Log response status
352    ///     ..Default::default()
353    /// }));
354    /// ```
355    ///
356    /// ## Default Behavior
357    ///
358    /// - Logs to the `info` level
359    /// - Includes HTTP method, path, and response status
360    /// - Applied to all routes ("/")
361    /// - Executed as post-middleware (after route handling)
362    #[cfg(feature = "logger")]
363    pub fn use_logger(&mut self, config: Option<LoggerConfig>) -> &mut Self {
364        self.middlewares.push(Middleware {
365            func: Self::middleware_from_closure(logger(config)),
366            path: "/".to_string(),
367            middleware_type: MiddlewareType::Post,
368        });
369        self
370    }
371
372    /// Adds a CORS (Cross-Origin Resource Sharing) middleware to the application.
373    ///
374    /// CORS middleware handles cross-origin requests by setting appropriate headers
375    /// and responding to preflight OPTIONS requests. This is essential for web applications
376    /// that need to accept requests from different domains.
377    ///
378    /// ## Arguments
379    ///
380    /// * `config` - Optional [`CorsConfig`] to customize CORS behavior. If `None`,
381    ///   permissive default settings are used.
382    ///
383    /// ## Example
384    ///
385    /// ```
386    /// use ripress::app::App;
387    /// use ripress::middlewares::cors::CorsConfig;
388    ///
389    /// let mut app = App::new();
390    ///
391    /// // Use permissive default CORS settings (allows all origins)
392    /// app.use_cors(None);
393    ///
394    /// // Use custom CORS configuration
395    /// app.use_cors(Some(CorsConfig {
396    ///     allowed_origin: "https://example.com",
397    ///     allowed_methods: "GET, POST, PUT, DELETE, OPTIONS, HEAD",
398    ///     allowed_headers: "Content-Type, Authorization",
399    ///     ..Default::default()
400    /// }));
401    /// ```
402    ///
403    /// ## Default Behavior
404    ///
405    /// - Allows all origins (`*`)
406    /// - Allows common HTTP methods
407    /// - Applied to all routes ("/")
408    /// - Executed as pre-middleware
409    /// - Automatically handles OPTIONS preflight requests
410    pub fn use_cors(&mut self, config: Option<CorsConfig>) -> &mut Self {
411        self.middlewares.push(Middleware {
412            func: Self::middleware_from_closure(cors(config)),
413            path: "/".to_string(),
414            middleware_type: MiddlewareType::Pre,
415        });
416        self
417    }
418
419    /// Adds a request body size limit middleware to the application.
420    ///
421    /// This middleware enforces a maximum size limit on incoming request bodies to prevent
422    /// memory exhaustion attacks and manage resource usage. Requests exceeding the limit
423    /// are rejected with a 413 Payload Too Large status.
424    ///
425    /// ## Arguments
426    ///
427    /// * `config` - Optional maximum size in bytes for request bodies. If `None`,
428    ///   the default limit is 1 MB (1,048,576 bytes).
429    ///
430    /// ## Example
431    ///
432    /// ```
433    /// use ripress::app::App;
434    ///
435    /// let mut app = App::new();
436    ///
437    /// // Use the default 1 MB limit
438    /// app.use_body_limit(None);
439    ///
440    /// // Set a custom limit (e.g., 2 MB for file uploads)
441    /// app.use_body_limit(Some(2 * 1024 * 1024));
442    ///
443    /// // Very restrictive limit for API endpoints (100 KB)
444    /// app.use_body_limit(Some(100 * 1024));
445    /// ```
446    ///
447    /// ## Behavior
448    ///
449    /// - Applied to all routes ("/")
450    /// - Executed as pre-middleware (before route processing)
451    /// - Returns 413 Payload Too Large for requests exceeding the limit
452    /// - Does not affect GET requests or requests without bodies
453    pub fn use_body_limit(&mut self, config: Option<usize>) -> &mut Self {
454        self.middlewares.push(Middleware {
455            func: Self::middleware_from_closure(body_limit(config)),
456            path: "/".to_string(),
457            middleware_type: MiddlewareType::Pre,
458        });
459        self
460    }
461
462    #[cfg(feature = "with-wynd")]
463    /// Adds WebSocket middleware to the application using the Wynd WebSocket library.
464    ///
465    /// This method enables WebSocket support for your application by integrating with
466    /// the Wynd WebSocket library. WebSocket connections will be handled at the specified path.
467    ///
468    /// ## Feature Requirement
469    ///
470    /// This method is only available when the `with-wynd` feature is enabled in your `Cargo.toml`:
471    ///
472    /// ```toml
473    /// [dependencies]
474    /// ripress = { version = "*", features = ["with-wynd"] }
475    /// wynd = { version = "*", features = ["with-ripress"] }
476    /// ```
477    ///
478    /// ## Arguments
479    ///
480    /// * `path` - The path where WebSocket connections should be accepted (e.g., "/ws", "/websocket")
481    /// * `handler` - A Wynd WebSocket handler function that processes WebSocket connections
482    ///
483    /// ## Example
484    ///
485    /// ```ignore
486    /// use ripress::{app::App, types::RouterFns};
487    /// use wynd::wynd::Wynd;
488    ///
489    /// #[tokio::main]
490    /// async fn main() {
491    ///     let mut app = App::new();
492    ///     let mut wynd = Wynd::new();
493    ///
494    ///     // Configure WebSocket event handlers
495    ///     wynd.on_connection(|conn| async move {
496    ///         println!("New WebSocket connection");
497    ///
498    ///         conn.on_text(|event, handle| async move {
499    ///             println!("Received message: {}", event.data);
500    ///             // Echo the message back
501    ///             handle.send_text(event.data).await.ok();
502    ///         });
503    ///
504    ///         conn.on_close(|_event| async move {
505    ///             println!("WebSocket connection closed");
506    ///         });
507    ///     });
508    ///
509    ///     // Add regular HTTP routes
510    ///     app.get("/", |_, res| async move {
511    ///         res.ok().text("WebSocket server running")
512    ///     });
513    ///
514    ///     // Add WebSocket support at /ws
515    ///     app.use_wynd("/ws", wynd.handler());
516    ///
517    ///     app.listen(3000, || {
518    ///         println!("Server with WebSocket support running on http://localhost:3000");
519    ///         println!("WebSocket endpoint: ws://localhost:3000/ws");
520    ///     }).await;
521    /// }
522    /// ```
523    ///
524    /// ## Client Connection
525    ///
526    /// Clients can connect to the WebSocket endpoint using:
527    ///
528    /// ```javascript
529    /// const ws = new WebSocket('ws://localhost:3000/ws');
530    /// ws.onmessage = (event) => console.log('Received:', event.data);
531    /// ws.send('Hello WebSocket!');
532    /// ```
533
534    pub fn use_wynd<F, Fut>(&mut self, path: &'static str, handler: F) -> &mut Self
535    where
536        F: Fn(hyper::Request<Full<Bytes>>) -> Fut + Send + Sync + 'static,
537        Fut: std::future::Future<Output = hyper::Result<hyper::Response<Full<hyper::body::Bytes>>>>
538            + Send
539            + 'static,
540    {
541        self.wynd_middleware = Some(WyndMiddleware {
542            func: Self::wynd_middleware_from_closure(handler),
543            path: path.to_string(),
544        });
545        self
546    }
547
548    /// Adds a rate limiting middleware to the application.
549    ///
550    /// Rate limiting helps protect your application from abuse by limiting the number
551    /// of requests a client can make within a specified time window. Requests exceeding
552    /// the limit are rejected with a 429 Too Many Requests status.
553    ///
554    /// ## Arguments
555    ///
556    /// * `config` - Optional [`RateLimiterConfig`] to customize rate limiting behavior.
557    ///   If `None`, default settings are used.
558    ///
559    /// ## Example
560    ///
561    /// ```no_run
562    /// use ripress::app::App;
563    /// use ripress::middlewares::rate_limiter::RateLimiterConfig;
564    /// use std::time::Duration;
565    ///
566    ///
567    /// let mut app = App::new();
568    ///
569    /// // Use default rate limiting (typically 100 requests per minute)
570    /// app.use_rate_limiter(None);
571    ///
572    /// // Custom rate limiting configuration
573    /// app.use_rate_limiter(Some(RateLimiterConfig {
574    ///     max_requests: 10,                    // Allow 10 requests
575    ///     window_ms: Duration::from_secs(60),     // Per 60 seconds
576    ///     message: "Rate limit exceeded".to_string(),
577    ///     ..Default::default()
578    /// }));
579    /// ```
580    ///
581    /// ## Default Behavior
582    ///
583    /// - Applied to all routes ("/")
584    /// - Executed as pre-middleware
585    /// - Uses client IP address for rate limiting
586    /// - Returns 429 Too Many Requests when limit is exceeded
587    /// - Includes rate limit headers in responses
588    ///
589    /// ## Rate Limit Headers
590    ///
591    /// The middleware adds these headers to responses:
592    /// - `X-RateLimit-Limit`: Maximum requests allowed
593    /// - `X-RateLimit-Remaining`: Requests remaining in current window
594    /// - `X-RateLimit-Reset`: Time when the rate limit window resets
595    pub fn use_rate_limiter(&mut self, config: Option<RateLimiterConfig>) -> &mut Self {
596        self.middlewares.push(Middleware {
597            func: Self::middleware_from_closure(rate_limiter(config)),
598            path: "/".to_string(),
599            middleware_type: MiddlewareType::Pre,
600        });
601        self
602    }
603
604    /// Adds a security middleware (shield) to the application.
605    ///
606    /// The shield middleware helps protect your application from common web vulnerabilities
607    /// by setting various HTTP security headers and applying security best practices. This
608    /// is essential for production applications.
609    ///
610    /// ## Arguments
611    ///
612    /// * `config` - Optional [`ShieldConfig`] to customize the shield middleware's behavior.
613    ///   If `None`, secure default settings are applied.
614    ///
615    /// ## Example
616    ///
617    /// ```
618    /// use ripress::app::App;
619    /// use ripress::middlewares::shield::{ShieldConfig, Hsts};
620    ///
621    /// let mut app = App::new();
622    ///
623    /// // Use default shield settings (recommended for most applications)
624    /// app.use_shield(None);
625    ///
626    /// // Custom shield configuration
627    /// app.use_shield(Some(ShieldConfig {
628    ///     hsts: Hsts {
629    ///         enabled: true,
630    ///         max_age: 31536000,           // 1 year
631    ///         include_subdomains: true,
632    ///         preload: true,
633    ///         ..Default::default()
634    ///     },
635    ///     ..Default::default()
636    /// }));
637    /// ```
638    ///
639    /// ## Security Headers Applied
640    ///
641    /// The shield middleware can set the following security headers:
642    ///
643    /// - `Strict-Transport-Security`: Forces HTTPS connections
644    /// - `X-Content-Type-Options`: Prevents MIME type sniffing
645    /// - `X-Frame-Options`: Prevents clickjacking attacks
646    /// - `X-XSS-Protection`: Enables cross-site scripting filtering
647    /// - `Referrer-Policy`: Controls referrer information
648    /// - `Content-Security-Policy`: Prevents various injection attacks
649    ///
650    /// ## Default Behavior
651    ///
652    /// - Applied to all routes ("/")
653    /// - Executed as pre-middleware
654    /// - Uses secure defaults suitable for most web applications
655    /// - Can be customized per security requirements
656    pub fn use_shield(&mut self, config: Option<ShieldConfig>) -> &mut Self {
657        self.middlewares.push(Middleware {
658            func: Self::middleware_from_closure(shield(config)),
659            path: "/".to_string(),
660            middleware_type: MiddlewareType::Pre,
661        });
662        self
663    }
664
665    /// Adds a compression middleware to the application.
666    ///
667    /// Compression middleware automatically compresses response bodies using algorithms
668    /// like gzip or deflate, reducing bandwidth usage and improving response times for
669    /// clients that support compression.
670    ///
671    /// ## Arguments
672    ///
673    /// * `config` - Optional [`CompressionConfig`] to customize compression behavior.
674    ///   If `None`, default settings are used with common compression algorithms enabled.
675    ///
676    /// ## Example
677    ///
678    /// ```
679    /// use ripress::app::App;
680    /// use ripress::middlewares::compression::CompressionConfig;
681    ///
682    /// let mut app = App::new();
683    ///
684    /// // Use default compression settings (gzip, deflate)
685    /// app.use_compression(None);
686    ///
687    /// // Custom compression configuration
688    /// app.use_compression(Some(CompressionConfig {
689    ///     level: 6,                        // Compression level (0-9)
690    ///     threshold: 1024,                 // Minimum bytes to compress
691    ///     ..Default::default()
692    /// }));
693    /// ```
694    ///
695    /// ## Default Behavior
696    ///
697    /// - Applied to all routes ("/")
698    /// - Executed as post-middleware (after response generation)
699    /// - Supports gzip and deflate compression
700    /// - Automatically negotiates compression based on `Accept-Encoding` header
701    /// - Only compresses responses above a minimum size threshold
702    /// - Skips compression for already-compressed content types
703    ///
704    /// ## Content Type Handling
705    ///
706    /// By default, the middleware:
707    /// - Compresses text-based content (HTML, CSS, JavaScript, JSON, XML)
708    /// - Skips binary content that's already compressed (images, videos, archives)
709    /// - Respects the client's `Accept-Encoding` header preferences
710    /// - Adds appropriate `Content-Encoding` headers to compressed responses
711    #[cfg(feature = "compression")]
712    pub fn use_compression(&mut self, config: Option<CompressionConfig>) -> &mut Self {
713        self.middlewares.push(Middleware {
714            func: Self::middleware_from_closure(compression(config)),
715            path: "/".to_string(),
716            middleware_type: MiddlewareType::Post,
717        });
718        self
719    }
720
721    /// Converts a closure into a middleware handler function.
722    ///
723    /// This is an internal helper method that wraps user-provided middleware functions
724    /// into the expected format for the middleware system.
725    fn middleware_from_closure<F, Fut>(f: F) -> HandlerMiddleware
726    where
727        F: Fn(HttpRequest, HttpResponse) -> Fut + Send + Sync + 'static,
728        Fut: std::future::Future<Output = (HttpRequest, Option<HttpResponse>)> + Send + 'static,
729    {
730        Arc::new(move |req, res| box_future_middleware(f(req, res)))
731    }
732
733    #[cfg(feature = "with-wynd")]
734    /// Converts a WebSocket handler closure into a Wynd middleware handler.
735    ///
736    /// This is an internal helper method for the WebSocket functionality.
737    fn wynd_middleware_from_closure<F, Fut>(f: F) -> WyndMiddlewareHandler
738    where
739        F: Fn(hyper::Request<Full<Bytes>>) -> Fut + Send + Sync + 'static,
740        Fut: std::future::Future<Output = hyper::Result<hyper::Response<Full<hyper::body::Bytes>>>>
741            + Send
742            + 'static,
743    {
744        Arc::new(move |req| Box::pin(f(req)))
745    }
746
747    /// Mounts a [`Router`] at a specific base path, registering all of its routes onto the application.
748    ///
749    /// This method allows you to modularly organize and group routes using separate routers,
750    /// then attach them to your application. Each route registered with the router will be
751    /// prefixed by the router's base path. This is useful for API versioning, feature groupings,
752    /// or splitting logic into modules. The router's routes are incorporated into the main
753    /// application's route table, and will take precedence over static file handlers.
754    ///
755    /// # Example
756    /// ```
757    /// use ripress::{app::App, router::Router};
758    /// use ripress::{req::HttpRequest, res::HttpResponse};
759    /// use ripress::types::RouterFns;
760    ///
761    /// async fn v1_status(_req: HttpRequest, res: HttpResponse) -> HttpResponse {
762    ///     res.ok().json(serde_json::json!({"status": "ok"}))
763    /// }
764    ///
765    /// #[tokio::main]
766    /// async fn main() {
767    ///     let mut api_router = Router::new("/api/v1");
768    ///     api_router.get("/status", v1_status);
769    ///     
770    ///     let mut app = App::new();
771    ///     app.router(api_router);
772    /// }
773    /// ```
774    ///
775    /// # Arguments
776    ///
777    /// * `router` - The [`Router`] instance whose routes will be registered onto this application.
778    ///
779    /// # Panics
780    ///
781    /// This method does not panic.
782    pub fn router(&mut self, mut router: Router) {
783        let base_path = router.base_path;
784        for (path, methods) in router.routes() {
785            for (method, handler) in methods.to_owned() {
786                let full_path = format!("{}{}", base_path, path);
787                self.add_route(method, &full_path, move |req, res| (handler)(req, res));
788            }
789        }
790    }
791
792    /// Configures static file serving for the application.
793    ///
794    /// This method allows you to serve static assets (HTML, CSS, JavaScript, images, etc.)
795    /// from the filesystem. Files are served with appropriate MIME types, caching headers,
796    /// and ETag support for efficient client-side caching.
797    ///
798    /// ## Arguments
799    ///
800    /// * `path` - The URL path where static files should be mounted (e.g., "/public", "/static", "/")
801    /// * `file` - The filesystem directory path containing the static files (e.g., "./public", "./dist")
802    ///
803    /// ## Returns
804    ///
805    /// * `Ok(())` - If the static file configuration was successful
806    /// * `Err(&'static str)` - If there was a validation error with the provided paths
807    ///
808    /// ## Errors
809    ///
810    /// This method returns an error in the following cases:
811    /// - `file` parameter is "/" (serving from filesystem root is blocked for security)
812    /// - `path` parameter is empty
813    /// - `file` parameter is empty
814    /// - `path` parameter doesn't start with "/"
815    ///
816    /// ## Example
817    ///
818    /// ```
819    /// use ripress::app::App;
820    ///
821    /// let mut app = App::new();
822    ///
823    /// // Serve files from ./public directory at /public URL path
824    /// app.static_files("/public", "./public").unwrap();
825    ///
826    /// // Serve CSS and JS assets
827    /// app.static_files("/assets", "./dist/assets").unwrap();
828    ///
829    /// // Serve a Single Page Application (SPA) from root
830    /// // API routes take precedence, static files serve as fallback
831    /// app.static_files("/", "./dist").unwrap();
832    ///
833    /// // Multiple static directories
834    /// app.static_files("/images", "./uploads/images").unwrap();
835    /// app.static_files("/docs", "./documentation").unwrap();
836    /// ```
837    ///
838    /// ## Behavior
839    ///
840    /// - **Route Precedence**: API routes defined with `get()`, `post()`, etc. take precedence over static files
841    /// - **Fallback Serving**: When mounted at "/", static files serve as fallback for unmatched routes
842    /// - **MIME Types**: Automatically sets appropriate `Content-Type` headers based on file extensions
843    /// - **Caching**: Includes `Cache-Control` and `ETag` headers for efficient browser caching
844    /// - **Security**: Prevents directory traversal attacks and blocks serving from filesystem root
845    ///
846    /// ## File System Layout Example
847    ///
848    /// ```text
849    /// project/
850    /// ├── src/main.rs
851    /// ├── public/           <- app.static_files("/public", "./public")
852    /// │   ├── index.html    <- Accessible at /public/index.html
853    /// │   ├── style.css     <- Accessible at /public/style.css
854    /// │   └── script.js     <- Accessible at /public/script.js
855    /// └── dist/             <- app.static_files("/", "./dist")
856    ///     ├── index.html    <- Accessible at / (fallback)
857    ///     └── favicon.ico   <- Accessible at /favicon.ico
858    /// ```
859    ///
860    /// ## Security Considerations
861    ///
862    /// - Never use "/" as the `file` parameter - this is blocked for security reasons
863    /// - Use specific directories like "./public" or "./assets"
864    /// - The static file server prevents directory traversal (../) attacks automatically
865    /// - Consider using a reverse proxy like nginx for serving static files in production
866    pub fn static_files(
867        &mut self,
868        path: &'static str,
869        file: &'static str,
870    ) -> Result<(), &'static str> {
871        // Validate inputs
872        if file == "/" {
873            return Err("Serving from filesystem root '/' is not allowed for security reasons");
874        }
875        if path.is_empty() {
876            return Err("Mount path cannot be empty");
877        }
878        if file.is_empty() {
879            return Err("File path cannot be empty");
880        }
881        // Require paths to start with '/'
882        if !path.starts_with('/') {
883            return Err("Mount path must start with '/'");
884        }
885        self.static_files.insert(path, file);
886        Ok(())
887    }
888
889    /// Starts the HTTP server and begins listening for incoming requests.
890    ///
891    /// This method builds the complete router with all configured routes, middleware,
892    /// and static file handlers, then starts the HTTP server on the specified port.
893    /// The server runs indefinitely until the process is terminated.
894    ///
895    /// ## Arguments
896    ///
897    /// * `port` - The port number to listen on (e.g., 3000, 8080)
898    /// * `cb` - A callback function that's executed once the server is ready to accept connections
899    ///
900    /// ## Example
901    ///
902    /// ```no_run
903    /// use ripress::app::App;
904    /// use ripress::types::RouterFns;
905    ///
906    /// #[tokio::main]
907    /// async fn main() {
908    ///     let mut app = App::new();
909    ///
910    ///     app.get("/", |_req, res| async move {
911    ///         res.ok().text("Hello, World!")
912    ///     });
913    ///
914    ///     app.get("/health", |_req, res| async move {
915    ///         res.ok().json(serde_json::json!({"status": "healthy"}))
916    ///     });
917    ///
918    ///     // Start server with startup message
919    ///     app.listen(3000, || {
920    ///         println!("🚀 Server running on http://localhost:3000");
921    ///         println!("📊 Health check: http://localhost:3000/health");
922    ///     }).await;
923    /// }
924    /// ```
925    ///
926    /// ## Server Initialization Order
927    ///
928    /// 1. **WebSocket Middleware**: Applied first (if `wynd` feature is enabled)
929    /// 2. **Application Middleware**: Applied in registration order
930    ///    - Pre-middleware (before route handlers)
931    ///    - Post-middleware (after route handlers)
932    /// 3. **API Routes**: Registered with exact path matching
933    /// 4. **Static File Routes**: Registered as fallback handlers
934    /// 5. **Error Handler**: Global error handling for the application
935    ///
936    /// ## Network Configuration
937    ///
938    /// - **Bind Address**: The server binds to `127.0.0.1:port` (localhost only)
939    /// - **Protocol**: HTTP/1.1 (HTTP/2 support may be added in future versions)
940    /// - **Concurrent Connections**: Handled asynchronously with Tokio
941    ///
942    /// ## Error Handling
943    ///
944    /// If the server fails to start (e.g., port already in use), the error is printed
945    /// to stderr and the process continues. You may want to handle this more gracefully:
946    ///
947    /// ```no_run
948    /// # use ripress::app::App;
949    /// # #[tokio::main]
950    /// # async fn main() {
951    /// # let app = App::new();
952    /// // The server will print errors but won't panic
953    /// app.listen(3000, || println!("Server starting...")).await;
954    /// // This line is reached if server fails to start
955    /// eprintln!("Server failed to start or has shut down");
956    /// # }
957    /// ```
958    ///
959    /// ## Production Considerations
960    ///
961    /// - Consider using environment variables for port configuration
962    /// - Implement graceful shutdown handling
963    /// - Use a process manager like systemd or PM2
964    /// - Configure reverse proxy (nginx, Apache) for production
965    /// - Enable logging middleware to monitor requests
966    pub async fn listen<F: FnOnce()>(&self, port: u16, cb: F) {
967        let mut router = routerify_ng::Router::<ApiError>::builder();
968
969        #[cfg(feature = "with-wynd")]
970        if let Some(middleware) = &self.wynd_middleware {
971            router = router.middleware(routerify_ng::Middleware::pre({
972                use crate::helpers::exec_wynd_middleware;
973
974                let middleware = middleware.clone();
975                move |req| exec_wynd_middleware(req, middleware.clone())
976            }));
977        }
978
979        // Apply middlewares first
980        for middleware in self.middlewares.iter() {
981            let middleware = middleware.clone();
982
983            if middleware.middleware_type == MiddlewareType::Post {
984                router = router.middleware(routerify_ng::Middleware::post_with_info({
985                    let middleware = middleware.clone();
986                    move |res, info| exec_post_middleware(res, middleware.clone(), info)
987                }));
988            } else {
989                router = router.middleware(routerify_ng::Middleware::pre({
990                    let middleware = middleware.clone();
991                    move |req| exec_pre_middleware(req, middleware.clone())
992                }));
993            }
994        }
995
996        // Register API routes FIRST (before static files)
997        // This ensures API routes take precedence over static file serving
998        for (path, methods) in &self.routes {
999            for (method, handler) in methods {
1000                let handler = Arc::clone(handler);
1001
1002                let method = match method {
1003                    HttpMethods::GET => Method::GET,
1004                    HttpMethods::POST => Method::POST,
1005                    HttpMethods::PUT => Method::PUT,
1006                    HttpMethods::DELETE => Method::DELETE,
1007                    HttpMethods::PATCH => Method::PATCH,
1008                    HttpMethods::HEAD => Method::HEAD,
1009                    HttpMethods::OPTIONS => Method::OPTIONS,
1010                };
1011
1012                router = router.add(path, vec![method], move |mut req| {
1013                    let handler = Arc::clone(&handler);
1014
1015                    async move {
1016                        let mut our_req = match HttpRequest::from_hyper_request(&mut req).await {
1017                            Ok(r) => r,
1018                            Err(e) => {
1019                                return Err(ApiError::Generic(
1020                                    HttpResponse::new().bad_request().text(e.to_string()),
1021                                ));
1022                            }
1023                        };
1024
1025                        req.params().iter().for_each(|(key, value)| {
1026                            our_req.set_param(key, value);
1027                        });
1028
1029                        let response = handler(our_req, HttpResponse::new()).await;
1030
1031                        let hyper_response = response.to_hyper_response().await;
1032                        // Infallible means this can never fail, so unwrap is safe
1033                        Ok(hyper_response.unwrap())
1034                    }
1035                });
1036            }
1037        }
1038
1039        for (mount_path, serve_from) in self.static_files.iter() {
1040            let serve_from = (*serve_from).to_string();
1041            let mount_root = (*mount_path).to_string();
1042
1043            let route_pattern_owned = if mount_root == "/" {
1044                "/*".to_string()
1045            } else {
1046                format!("{}/{}", mount_root, "*")
1047            };
1048
1049            let route_pattern: &'static str = Box::leak(route_pattern_owned.into_boxed_str());
1050
1051            let serve_from_clone = serve_from.clone();
1052            let mount_root_clone = mount_root.clone();
1053
1054            router = router.get(route_pattern, move |req| {
1055                let serve_from = serve_from_clone.clone();
1056                let mount_root = mount_root_clone.clone();
1057                async move {
1058                    match Self::serve_static_with_headers(req, mount_root, serve_from).await {
1059                        Ok(res) => Ok(res),
1060                        Err(e) => Err(ApiError::Generic(
1061                            HttpResponse::new()
1062                                .internal_server_error()
1063                                .text(e.to_string()),
1064                        )),
1065                    }
1066                }
1067            });
1068        }
1069
1070        router = router.err_handler(Self::error_handler);
1071        let router = router.build().unwrap();
1072        cb();
1073
1074        let addr = SocketAddr::from(([127, 0, 0, 1], port));
1075
1076        let listener = TcpListener::bind(addr).await;
1077
1078        if let Err(e) = listener {
1079            eprintln!("Error binding to address {}: {}", addr, e);
1080            return;
1081        }
1082
1083        let listener = listener.unwrap();
1084
1085        let router_service = Arc::new(RouterService::new(router).unwrap());
1086
1087        if self.graceful_shutdown {
1088            let mut shutdown = Box::pin(tokio::signal::ctrl_c());
1089
1090            loop {
1091                tokio::select! {
1092                    result = listener.accept() => {
1093                        match result {
1094                            Ok((stream, _)) => {
1095                                let service = Arc::clone(&router_service);
1096
1097                                tokio::task::spawn(async move {
1098                                    // Now service is Arc<RouterService> and not moved
1099                                     let request_service = match service.call(&stream).await {
1100                                             Ok(svc) => svc,
1101                                             Err(err) => {
1102                                                 eprintln!("Error creating per-connection service: {:?}", err);
1103                                                 return;
1104                                             }
1105                                         };
1106
1107
1108                                    // Wrap the stream in TokioIo for hyper
1109                                    let io = TokioIo::new(stream);
1110                                    let mut builder = Builder::new();
1111                                    builder.keep_alive(true);
1112
1113                                    // Serve the connection with upgrades enabled for WebSocket support
1114                                    let connection = builder.serve_connection(io, request_service).with_upgrades();
1115                                    if let Err(err) = connection.await {
1116                                        eprintln!("Error serving connection: {:?}", err);
1117                                    }
1118                                });
1119                            }
1120                            Err(e) => {
1121                                eprintln!("Error accepting connection: {}", e);
1122                            }
1123                        }
1124                    }
1125                    _ = shutdown.as_mut() => {
1126                        break;
1127                    }
1128                }
1129            }
1130        } else {
1131            loop {
1132                match listener.accept().await {
1133                    Ok((stream, _)) => {
1134                        let service = Arc::clone(&router_service);
1135
1136                        tokio::task::spawn(async move {
1137                            let request_service = match service.call(&stream).await {
1138                                Ok(svc) => svc,
1139                                Err(err) => {
1140                                    eprintln!("Error creating per-connection service: {:?}", err);
1141                                    return;
1142                                }
1143                            };
1144
1145                            // Wrap the stream in TokioIo for hyper
1146                            let io = TokioIo::new(stream);
1147                            let mut builder = Builder::new();
1148                            builder.keep_alive(true);
1149
1150                            // Serve the connection with upgrades enabled for WebSocket support
1151                            let connection = builder
1152                                .serve_connection(io, request_service)
1153                                .with_upgrades();
1154                            if let Err(err) = connection.await {
1155                                eprintln!("Error serving connection: {:?}", err);
1156                            }
1157                        });
1158                    }
1159                    Err(e) => {
1160                        eprintln!("Error accepting connection: {}", e);
1161                    }
1162                }
1163            }
1164        }
1165    }
1166
1167    /// Internal error handler for the router.
1168    ///
1169    /// This method processes routing errors and converts them into appropriate HTTP responses.
1170    /// It handles both generic API errors and unexpected system errors.
1171    pub(crate) async fn error_handler(
1172        err: routerify_ng::RouteError,
1173    ) -> Response<Full<hyper::body::Bytes>> {
1174        let api_err = err.downcast::<ApiError>().unwrap_or_else(|_| {
1175            return Box::new(ApiError::Generic(
1176                HttpResponse::new()
1177                    .internal_server_error()
1178                    .text("Unhandled error"),
1179            ));
1180        });
1181
1182        println!("api_err: {:?}", api_err);
1183
1184        // For WebSocket upgrades, we need to take ownership to avoid breaking the upgrade mechanism
1185        // Cloning the response breaks the upgrade connection, so we must move it
1186        match *api_err {
1187            ApiError::WebSocketUpgrade(response) => {
1188                // Return the response directly without cloning to preserve the upgrade mechanism
1189                response
1190            }
1191            ApiError::Generic(res) => {
1192                let hyper_res = <HttpResponse as Clone>::clone(&res)
1193                    .to_hyper_response()
1194                    .await
1195                    .map_err(ApiError::from)
1196                    .unwrap();
1197
1198                hyper_res
1199            }
1200        }
1201    }
1202
1203    /// Internal method for serving static files with proper headers and caching support.
1204    ///
1205    /// This method handles the complex logic of serving static files, including:
1206    /// - URL path rewriting to map mount points to filesystem paths
1207    /// - ETag-based conditional requests (304 Not Modified responses)
1208    /// - Proper caching headers
1209    /// - Error handling for missing files
1210    ///
1211    /// ## Arguments
1212    ///
1213    /// * `req` - The incoming HTTP request
1214    /// * `mount_root` - The URL path where static files are mounted
1215    /// * `fs_root` - The filesystem directory containing the static files
1216    ///
1217    /// ## Returns
1218    ///
1219    /// * `Ok(Response<Body>)` - Successfully served file or 304 Not Modified
1220    /// * `Err(std::io::Error)` - File not found or other I/O error
1221    pub(crate) async fn serve_static_with_headers<B>(
1222        req: Request<B>,
1223        mount_root: String,
1224        fs_root: String,
1225    ) -> Result<Response<Full<hyper::body::Bytes>>, std::io::Error>
1226    where
1227        B: hyper::body::Body<Data = hyper::body::Bytes> + Send + 'static,
1228        B::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
1229    {
1230        // Rewrite the request URI by stripping the mount_root prefix so that
1231        // "/static/index.html" maps to "fs_root/index.html" rather than
1232        // "fs_root/static/index.html".
1233        let (mut parts, body) = req.into_parts();
1234        let original_uri = parts.uri.clone();
1235        let original_path = original_uri.path();
1236        let if_none_match = parts
1237            .headers
1238            .get(header::IF_NONE_MATCH)
1239            .and_then(|v| v.to_str().ok())
1240            .map(|s| s.to_string());
1241
1242        let trimmed_path = if mount_root == "/" {
1243            // If mounting at root, serve the path as-is
1244            original_path
1245        } else if original_path.starts_with(&mount_root) {
1246            // Strip the mount root prefix, but ensure we don't create an empty path
1247            let remaining = &original_path[mount_root.len()..];
1248            if remaining.is_empty() { "/" } else { remaining }
1249        } else {
1250            // Path doesn't match mount root - this shouldn't happen in normal routing
1251            original_path
1252        };
1253
1254        let normalized_path = if trimmed_path.is_empty() {
1255            "/"
1256        } else {
1257            trimmed_path
1258        };
1259
1260        let new_path_and_query = if let Some(query) = original_uri.query() {
1261            format!("{}?{}", normalized_path, query)
1262        } else {
1263            normalized_path.to_string()
1264        };
1265
1266        parts.uri = match new_path_and_query.parse() {
1267            Ok(uri) => uri,
1268            Err(e) => {
1269                eprintln!(
1270                    "Error parsing URI: {} (original: {}, mount_root: {}, trimmed: {}, normalized: {})",
1271                    e, original_path, mount_root, trimmed_path, normalized_path
1272                );
1273                return Err(std::io::Error::new(
1274                    std::io::ErrorKind::InvalidInput,
1275                    format!("Invalid URI after rewriting: {}", e),
1276                ));
1277            }
1278        };
1279
1280        let rewritten_req = Request::from_parts(parts, body);
1281
1282        let static_service = Static::new(Path::new(fs_root.as_str()));
1283
1284        match static_service.serve(rewritten_req).await {
1285            Ok(mut response) => {
1286                response
1287                    .headers_mut()
1288                    .insert("Cache-Control", "public, max-age=86400".parse().unwrap());
1289                response
1290                    .headers_mut()
1291                    .insert("X-Served-By", "hyper-staticfile".parse().unwrap());
1292                // Handle conditional request with If-None-Match since hyper-staticfile 0.9
1293                // does not evaluate it. If ETag matches, return 304 with empty body.
1294                if let Some(if_none_match_value) = if_none_match {
1295                    if let Some(etag) = response.headers().get(header::ETAG) {
1296                        if let Ok(etag_value) = etag.to_str() {
1297                            if if_none_match_value == etag_value {
1298                                let mut builder =
1299                                    Response::builder().status(StatusCode::NOT_MODIFIED);
1300                                if let Some(h) = builder.headers_mut() {
1301                                    // carry forward ETag, Cache-Control, Last-Modified, etc.
1302                                    for (k, v) in response.headers().iter() {
1303                                        h.insert(k.clone(), v.clone());
1304                                    }
1305                                    h.remove(header::CONTENT_LENGTH);
1306                                }
1307                                return Ok(builder.body(Full::from(Bytes::new())).unwrap());
1308                            }
1309                        }
1310                    }
1311                }
1312                // Convert hyper_staticfile::Body to Full<Bytes>
1313                let (parts, body) = response.into_parts();
1314                let collected = body.collect().await.map_err(|e| {
1315                    std::io::Error::new(
1316                        std::io::ErrorKind::Other,
1317                        format!("Failed to collect body: {}", e),
1318                    )
1319                })?;
1320                let body_bytes = collected.to_bytes();
1321                let full_body = Full::from(body_bytes);
1322                Ok(Response::from_parts(parts, full_body))
1323            }
1324            Err(e) => Err(e),
1325        }
1326    }
1327
1328    /// Internal method for building a router instance.
1329    ///
1330    /// This is used internally for testing and development purposes.
1331    pub(crate) fn _build_router(&self) -> routerify_ng::Router<ApiError> {
1332        routerify_ng::Router::builder()
1333            .err_handler(Self::error_handler)
1334            .build()
1335            .unwrap()
1336    }
1337}