Skip to main content

rust_web_server/app/
mod.rs

1#[cfg(test)]
2mod tests;
3
4pub mod controller;
5
6use crate::app::controller::favicon::FaviconController;
7use crate::app::controller::health::HealthController;
8use crate::app::controller::ready::ReadyController;
9use crate::app::controller::metrics::MetricsController;
10use crate::app::controller::file::initiate::FileUploadInitiateController;
11use crate::app::controller::form::get_method::FormGetMethodController;
12use crate::app::controller::form::multipart_enctype_post_method::FormMultipartEnctypePostMethodController;
13use crate::app::controller::form::url_encoded_enctype_post_method::FormUrlEncodedEnctypePostMethodController;
14use crate::app::controller::index::IndexController;
15use crate::app::controller::not_found::NotFoundController;
16use crate::app::controller::script::ScriptController;
17use crate::app::controller::static_resource::StaticResourceController;
18use crate::app::controller::style::StyleController;
19use crate::application::Application;
20use crate::controller::Controller;
21use crate::core::New;
22use crate::header::Header;
23use crate::middleware::{Middleware, WithMiddleware};
24use crate::request::Request;
25use crate::response::{Response, STATUS_CODE_REASON_PHRASE};
26use crate::server::ConnectionInfo;
27use crate::state::AppWithState;
28
29/// The built-in HTTP application. Serves static files, favicons, forms,
30/// file uploads, health probes, metrics, and a 404 fallback.
31///
32/// Use as-is or compose with the framework's building blocks:
33///
34/// ```rust,no_run
35/// use rust_web_server::app::App;
36/// use rust_web_server::middleware::{WithMiddleware, RateLimitLayer};
37/// use rust_web_server::core::New;
38///
39/// // Middleware stack around the built-in app
40/// let app = App::new().wrap(RateLimitLayer);
41/// ```
42///
43/// For user-defined routes with shared state, call [`App::with_state`]:
44///
45/// ```rust,no_run
46/// use rust_web_server::app::App;
47/// use rust_web_server::response::{Response, STATUS_CODE_REASON_PHRASE};
48/// use rust_web_server::core::New;
49///
50/// struct State { version: &'static str }
51///
52/// let app = App::with_state(State { version: "1.0" })
53///     .get("/version", |_req, _params, _conn, state| {
54///         let mut r = Response::new();
55///         r.status_code = *STATUS_CODE_REASON_PHRASE.n200_ok.status_code;
56///         r.reason_phrase = STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase.to_string();
57///         r
58///     });
59/// ```
60#[derive(Copy, Clone)]
61pub struct App {}
62
63impl New for App {
64    fn new() -> Self {
65        App{}
66    }
67}
68
69impl Application for App {
70    fn execute(&self, request: &Request, connection: &ConnectionInfo) -> Result<Response, String> {
71        let header_list = Header::get_header_list(&request);
72
73        let mut response: Response = Response::get_response(
74            STATUS_CODE_REASON_PHRASE.n501_not_implemented,
75            Some(header_list),
76            None
77        );
78
79
80
81        if IndexController::is_matching(&request, connection) {
82            response = IndexController::process(&request, response, connection);
83            return Ok(response)
84        }
85
86        if StyleController::is_matching(&request, connection) {
87            response = StyleController::process(&request, response, connection);
88            return Ok(response)
89        }
90
91        if ScriptController::is_matching(&request, connection) {
92            response = ScriptController::process(&request, response, connection);
93            return Ok(response)
94        }
95
96        if FileUploadInitiateController::is_matching(&request, connection) {
97            response = FileUploadInitiateController::process(&request, response, connection);
98            return Ok(response)
99        }
100
101        if FormUrlEncodedEnctypePostMethodController::is_matching(&request, connection) {
102            response = FormUrlEncodedEnctypePostMethodController::process(&request, response, connection);
103            return Ok(response)
104        }
105
106        if FormGetMethodController::is_matching(&request, connection) {
107            response = FormGetMethodController::process(&request, response, connection);
108            return Ok(response)
109        }
110
111        if FormMultipartEnctypePostMethodController::is_matching(&request, connection) {
112            response = FormMultipartEnctypePostMethodController::process(&request, response, connection);
113            return Ok(response)
114        }
115
116        if HealthController::is_matching(&request, connection) {
117            response = HealthController::process(&request, response, connection);
118            return Ok(response)
119        }
120
121        if ReadyController::is_matching(&request, connection) {
122            response = ReadyController::process(&request, response, connection);
123            return Ok(response)
124        }
125
126        if MetricsController::is_matching(&request, connection) {
127            response = MetricsController::process(&request, response, connection);
128            return Ok(response)
129        }
130
131        if FaviconController::is_matching(&request, connection) {
132            response = FaviconController::process(&request, response, connection);
133            return Ok(response)
134        }
135
136        if StaticResourceController::is_matching(&request, connection) {
137            response = StaticResourceController::process(&request, response, connection);
138            return Ok(response)
139        }
140
141        if NotFoundController::is_matching(&request, connection) {
142            response = NotFoundController::process(&request, response, connection);
143            return Ok(response)
144        }
145
146
147        Ok(response)
148    }
149}
150
151impl App {
152    /// Dispatch `request` through the controller chain and return the response.
153    ///
154    /// This is a convenience wrapper over [`Application::execute`] that uses a
155    /// synthetic loopback [`ConnectionInfo`]. Use it in tests or when no real
156    /// connection context is available. Prefer [`TestClient`] for structured
157    /// test code.
158    ///
159    /// [`TestClient`]: crate::test_client::TestClient
160    pub fn handle_request(request: Request) -> (Response, Request) {
161        use crate::server::Address;
162        let conn = ConnectionInfo {
163            client: Address { ip: "127.0.0.1".to_string(), port: 0 },
164            server: Address { ip: "127.0.0.1".to_string(), port: 7878 },
165            request_size: 16000,
166        };
167        let app = App::new();
168        let response = app.execute(&request, &conn).unwrap_or_else(|_| {
169            let header_list = Header::get_header_list(&request);
170            Response::get_response(
171                STATUS_CODE_REASON_PHRASE.n500_internal_server_error,
172                Some(header_list),
173                None,
174            )
175        });
176        (response, request)
177    }
178
179    /// Create a state-aware application. Routes registered on the returned
180    /// [`AppWithState<S>`] are tried first; unmatched requests fall through to
181    /// the built-in controller chain (static files, health probes, etc.).
182    ///
183    /// The state is stored as `Arc<S>` and shared across all handlers.
184    ///
185    /// # Example
186    ///
187    /// ```rust,no_run
188    /// use rust_web_server::app::App;
189    /// use rust_web_server::response::{Response, STATUS_CODE_REASON_PHRASE};
190    /// use rust_web_server::core::New;
191    ///
192    /// struct Db { url: String }
193    ///
194    /// let app = App::with_state(Db { url: "postgres://...".to_string() })
195    ///     .get("/ping", |_req, _params, _conn, db| {
196    ///         let mut r = Response::new();
197    ///         r.status_code = *STATUS_CODE_REASON_PHRASE.n200_ok.status_code;
198    ///         r.reason_phrase = STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase.to_string();
199    ///         r
200    ///     });
201    /// ```
202    pub fn with_state<S: Send + Sync + 'static>(state: S) -> AppWithState<S> {
203        AppWithState::new(state)
204    }
205
206    /// Wrap this application in a middleware layer.
207    ///
208    /// Returns a [`WithMiddleware<App>`] that runs `layer` before every
209    /// request. Chain `.wrap()` calls to stack multiple layers:
210    ///
211    /// ```rust,no_run
212    /// use rust_web_server::app::App;
213    /// use rust_web_server::middleware::RateLimitLayer;
214    /// use rust_web_server::core::New;
215    ///
216    /// let app = App::new().wrap(RateLimitLayer);
217    /// ```
218    pub fn wrap<M: Middleware + 'static>(self, layer: M) -> WithMiddleware<App> {
219        WithMiddleware::new(self).wrap(layer)
220    }
221}