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