Skip to main content

sova_core/app/
hooks.rs

1use super::App;
2use crate::error::Result;
3use crate::handler::BoxFuture;
4use crate::request::Request;
5use crate::response::Response;
6use crate::router::{compile_router, CompiledRouter};
7use crate::service::BoxedService;
8use crate::state::StateMap;
9use crate::upgrade::UpgradeBudget;
10use bytes::Bytes;
11use http::Method;
12use std::sync::Arc;
13use std::time::Duration;
14use tokio::sync::Semaphore;
15
16pub(crate) type StartupHook =
17    Arc<dyn Fn(Arc<StateMap>) -> BoxFuture<Result<()>> + Send + Sync>;
18pub(crate) type ShutdownHook = Arc<dyn Fn() -> BoxFuture<()> + Send + Sync>;
19
20/// Compiled app ready to handle requests without recompiling the router.
21#[derive(Clone)]
22pub struct Server {
23    pub(crate) inner: Arc<AppInner>,
24    pub(crate) startups: Vec<StartupHook>,
25    pub(crate) shutdowns: Vec<ShutdownHook>,
26}
27
28impl Server {
29    /// Shared application state map (same as request `state`).
30    pub fn state(&self) -> Arc<StateMap> {
31        self.inner.state()
32    }
33
34    /// Run startup hooks without consuming them (safe to call multiple times).
35    #[cfg(any(test, feature = "testing"))]
36    pub async fn run_startup(&self) -> Result<Arc<StateMap>> {
37        let state = self.state();
38        for hook in &self.startups {
39            hook(Arc::clone(&state)).await?;
40        }
41        Ok(state)
42    }
43
44    /// Run shutdown hooks without consuming them.
45    #[cfg(any(test, feature = "testing"))]
46    pub async fn run_shutdown(&self) {
47        for hook in &self.shutdowns {
48            hook().await;
49        }
50    }
51
52    /// Handle a request (tests / embedded). Injects router state.
53    pub async fn handle(&self, mut req: Request) -> Response {
54        req.state = Arc::clone(&self.inner.compiled.state);
55        self.inner.compiled.dispatch(req).await
56    }
57
58    /// Convenience for unit tests without headers.
59    /// Custom headers: [`Request::builder`] + [`Self::handle`].
60    pub async fn handle_request(&self, method: Method, path: &str, body: &str) -> Response {
61        let req = Request::builder()
62            .method(method)
63            .path(path)
64            .body(Bytes::from(body.to_string()))
65            .build();
66        self.handle(req).await
67    }
68}
69
70pub(crate) struct ListenParts {
71    pub(crate) inner: AppInner,
72    pub(crate) startups: Vec<StartupHook>,
73    pub(crate) shutdowns: Vec<ShutdownHook>,
74    pub(crate) services: Vec<BoxedService>,
75    /// When false (CLI default), BackgroundServices are not started.
76    pub(crate) start_services: bool,
77}
78
79impl App {
80    fn validate_plugin_requires(&self) -> Result<()> {
81        if let Some((plugin, dep)) = self.missing_plugin_requires.first().copied() {
82            return Err(crate::error::Error::Internal(format!(
83                "plugin `{plugin}` requires `{dep}`; install `{dep}` before `{plugin}`"
84            )));
85        }
86        if let Some(msg) = self.plugin_sdk_errors.first() {
87            return Err(crate::error::Error::Internal(msg.clone()));
88        }
89        Ok(())
90    }
91
92    /// Compile routes once into a [`Server`]. Prefer this over repeated [`App::handle`].
93    pub fn build(&self) -> Result<Server> {
94        self.validate_plugin_requires()?;
95        let router = self.router.clone_for_compile();
96        router
97            .check_route_values(&self.router.state, &self.installed_plugins)
98            .map_err(crate::error::Error::Internal)?;
99        let explain = router.explain();
100        let route_count = router.route_entries().len();
101        let compiled = Arc::new(compile_router(router)?);
102        Ok(Server {
103            inner: Arc::new(AppInner::from_settings(
104                compiled,
105                route_count,
106                explain,
107                AppSettings::from(self),
108            )),
109            startups: self.on_startup.clone(),
110            shutdowns: self.on_shutdown.clone(),
111        })
112    }
113
114    pub(crate) fn into_listen_parts(mut self) -> Result<ListenParts> {
115        self.validate_plugin_requires()?;
116        let services = std::mem::take(&mut self.services);
117        let startups = self.on_startup.clone();
118        let shutdowns = self.on_shutdown.clone();
119        let start_services = !self.cli_mode || self.service_in_cli;
120
121        self.router
122            .check_route_values(&self.router.state, &self.installed_plugins)
123            .map_err(crate::error::Error::Internal)?;
124        let explain = self.router.explain();
125        let route_count = self.router.route_entries().len();
126        let settings = AppSettings::from(&self);
127        let router = self.router;
128        let compiled = Arc::new(compile_router(router)?);
129
130        Ok(ListenParts {
131            inner: AppInner::from_settings(compiled, route_count, explain, settings),
132            startups,
133            shutdowns,
134            services,
135            start_services,
136        })
137    }
138}
139
140pub(crate) struct AppSettings {
141    pub max_body_size: usize,
142    pub max_connections: usize,
143    pub max_upgraded_connections: usize,
144    pub max_concurrent_streams: usize,
145    pub max_headers: usize,
146    pub max_buf_size: Option<usize>,
147    pub request_timeout: Option<Duration>,
148    pub header_read_timeout: Duration,
149    pub idle_timeout: Duration,
150    pub drain_timeout: Duration,
151    pub keep_alive: bool,
152    pub trust_proxy: bool,
153    pub reuseport: bool,
154    pub hsts: bool,
155    pub alt_svc: Option<String>,
156}
157
158impl From<&App> for AppSettings {
159    fn from(app: &App) -> Self {
160        Self {
161            max_body_size: app.max_body_size,
162            max_connections: app.max_connections,
163            max_upgraded_connections: app.max_upgraded_connections,
164            max_concurrent_streams: app.max_concurrent_streams,
165            max_headers: app.max_headers,
166            max_buf_size: app.max_buf_size,
167            request_timeout: app.request_timeout,
168            header_read_timeout: app.header_read_timeout,
169            idle_timeout: app.idle_timeout,
170            drain_timeout: app.drain_timeout,
171            keep_alive: app.keep_alive,
172            trust_proxy: app.trust_proxy,
173            reuseport: app.reuseport,
174            hsts: app.hsts,
175            alt_svc: app.alt_svc.clone(),
176        }
177    }
178}
179
180pub(crate) struct AppInner {
181    pub(crate) compiled: Arc<CompiledRouter>,
182    pub(crate) max_body_size: usize,
183    pub(crate) max_connections: usize,
184    pub(crate) max_upgraded: UpgradeBudget,
185    pub(crate) max_concurrent_streams: usize,
186    pub(crate) max_headers: usize,
187    pub(crate) max_buf_size: Option<usize>,
188    pub(crate) request_timeout: Option<Duration>,
189    pub(crate) header_read_timeout: Duration,
190    pub(crate) idle_timeout: Duration,
191    pub(crate) drain_timeout: Duration,
192    pub(crate) keep_alive: bool,
193    pub(crate) trust_proxy: bool,
194    pub(crate) reuseport: bool,
195    pub(crate) hsts: bool,
196    pub(crate) alt_svc: Option<String>,
197    pub(crate) route_count: usize,
198    pub(crate) explain: String,
199}
200
201impl AppInner {
202    fn from_settings(
203        compiled: Arc<CompiledRouter>,
204        route_count: usize,
205        explain: String,
206        s: AppSettings,
207    ) -> Self {
208        Self {
209            compiled,
210            max_body_size: s.max_body_size,
211            max_connections: s.max_connections,
212            max_upgraded: UpgradeBudget(Arc::new(Semaphore::new(s.max_upgraded_connections))),
213            max_concurrent_streams: s.max_concurrent_streams,
214            max_headers: s.max_headers,
215            max_buf_size: s.max_buf_size,
216            request_timeout: s.request_timeout,
217            header_read_timeout: s.header_read_timeout,
218            idle_timeout: s.idle_timeout,
219            drain_timeout: s.drain_timeout,
220            keep_alive: s.keep_alive,
221            trust_proxy: s.trust_proxy,
222            reuseport: s.reuseport,
223            hsts: s.hsts,
224            alt_svc: s.alt_svc,
225            route_count,
226            explain,
227        }
228    }
229
230    pub(crate) async fn handle(&self, req: Request) -> Response {
231        self.compiled.dispatch(req).await
232    }
233
234    pub(crate) fn state(&self) -> Arc<StateMap> {
235        Arc::clone(&self.compiled.state)
236    }
237
238    pub(crate) fn conn_header_timeout(&self) -> Duration {
239        self.header_read_timeout.min(self.idle_timeout)
240    }
241}