1mod auth;
14mod command;
15mod handlers;
16mod query;
17mod reload;
18mod response;
19mod state;
20mod trace;
21
22pub use auth::{CommandTokenVerifier, HttpCommandAuth, RequestValidationDetails};
23pub use reload::{
24 HttpReloadPhase, HttpReloadPolicy, HttpReloadSnapshot, PreparedRuntimeHttpGeneration,
25 ReloadableRuntimeHttpHost, RuntimeHttpReloadError,
26};
27pub use state::{
28 CommandCapabilityPolicy, CommandCapabilityPolicyError, HttpApiConfig, RuntimeStaticInfo,
29 SyncLogView, SyncLogViewError,
30};
31
32#[cfg(test)]
33use crate::command_contract::{CommandRequest, CommandResponse, CommandResponseEvent};
34use crate::ApiRouter;
35use appcore_core::{RuntimeController, RuntimeOperationalMode};
36use axum::extract::DefaultBodyLimit;
37use axum::http::StatusCode;
38use axum::routing::{get, post};
39use axum::Router;
40use command::command_handler;
41use handlers::{
42 diagnostics_handler, health_handler, private_status_handler, public_status_handler,
43 status_handler,
44};
45use parking_lot::Mutex;
46use query::query_handler;
47use state::HttpState;
48use std::io;
49use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
50use std::sync::Arc;
51use std::time::Duration;
52
53pub struct RuntimeHttpHost {
55 config: HttpApiConfig,
56 router: Router,
57}
58
59impl RuntimeHttpHost {
60 pub fn new(config: HttpApiConfig, static_info: RuntimeStaticInfo) -> Self {
62 Self::with_runtime_state(config, static_info, RuntimeHttpStateParts::default())
63 }
64
65 pub fn with_controller(
67 config: HttpApiConfig,
68 static_info: RuntimeStaticInfo,
69 controller: Arc<Mutex<RuntimeController>>,
70 ) -> Self {
71 Self::with_runtime_state(
72 config,
73 static_info,
74 RuntimeHttpStateParts {
75 controller: Some(controller),
76 ..RuntimeHttpStateParts::default()
77 },
78 )
79 }
80
81 pub fn with_runtime_state_and_auth(
83 config: HttpApiConfig,
84 static_info: RuntimeStaticInfo,
85 controller: Arc<Mutex<RuntimeController>>,
86 sync_log: Option<Arc<dyn SyncLogView>>,
87 tick_counter: Option<Arc<AtomicU64>>,
88 auth: HttpCommandAuth,
89 ) -> Self {
90 Self::with_runtime_state(
91 config,
92 static_info,
93 RuntimeHttpStateParts {
94 controller: Some(controller),
95 sync_log,
96 tick_counter,
97 auth,
98 ..RuntimeHttpStateParts::default()
99 },
100 )
101 }
102
103 pub fn with_runtime_state_auth_and_operation_mode(
105 config: HttpApiConfig,
106 static_info: RuntimeStaticInfo,
107 controller: Arc<Mutex<RuntimeController>>,
108 sync_log: Option<Arc<dyn SyncLogView>>,
109 tick_counter: Option<Arc<AtomicU64>>,
110 auth: HttpCommandAuth,
111 operation_mode: Arc<Mutex<appcore_core::RuntimeOperationalMode>>,
112 ) -> Self {
113 Self::with_runtime_state(
114 config,
115 static_info,
116 RuntimeHttpStateParts {
117 controller: Some(controller),
118 sync_log,
119 tick_counter,
120 operation_mode: Some(operation_mode),
121 auth,
122 ..RuntimeHttpStateParts::default()
123 },
124 )
125 }
126
127 pub fn with_runtime_state_auth_and_app_queries(
129 config: HttpApiConfig,
130 static_info: RuntimeStaticInfo,
131 controller: Arc<Mutex<RuntimeController>>,
132 sync_log: Option<Arc<dyn SyncLogView>>,
133 tick_counter: Option<Arc<AtomicU64>>,
134 auth: HttpCommandAuth,
135 app_query_router: Arc<Mutex<ApiRouter>>,
136 ) -> Self {
137 Self::with_runtime_state(
138 config,
139 static_info,
140 RuntimeHttpStateParts {
141 controller: Some(controller),
142 sync_log,
143 tick_counter,
144 app_query_router: Some(app_query_router),
145 auth,
146 ..RuntimeHttpStateParts::default()
147 },
148 )
149 }
150
151 pub fn with_state_parts(
153 config: HttpApiConfig,
154 static_info: RuntimeStaticInfo,
155 parts: RuntimeHttpStateParts,
156 ) -> Self {
157 Self::with_runtime_state(config, static_info, parts)
158 }
159
160 fn with_runtime_state(
161 config: HttpApiConfig,
162 static_info: RuntimeStaticInfo,
163 parts: RuntimeHttpStateParts,
164 ) -> Self {
165 if let Some(router) = &parts.app_query_router {
166 router.lock().freeze_queries();
167 }
168 let state = HttpState {
169 static_info,
170 controller: parts.controller,
171 app_query_router: parts.app_query_router,
172 sync_log: parts.sync_log,
173 tick_counter: parts.tick_counter,
174 operation_mode: parts.operation_mode,
175 command_policy: parts.command_policy,
176 supervisor: parts.supervisor,
177 auth: parts.auth,
178 max_payload_bytes: config.max_payload_bytes,
179 clock: Arc::new(appcore_core::SystemClock::new()),
180 };
181 let router = Router::new()
182 .route("/v1/health", get(health_handler))
183 .route("/v1/status", get(status_handler))
184 .route("/v1/status/public", get(public_status_handler))
185 .route("/v1/status/private", get(private_status_handler))
186 .route("/v1/diagnostics", get(diagnostics_handler))
187 .route("/v1/command", post(command_handler))
188 .route("/v1/query", post(query_handler))
189 .route("/health", get(update_required_handler))
190 .route("/status", get(update_required_handler))
191 .route("/status/public", get(update_required_handler))
192 .route("/status/private", get(update_required_handler))
193 .route("/diagnostics", get(update_required_handler))
194 .route("/command", post(update_required_handler))
195 .route("/query", post(update_required_handler))
196 .layer(DefaultBodyLimit::max(config.max_payload_bytes))
197 .with_state(state);
198 Self { config, router }
199 }
200
201 pub fn config(&self) -> &HttpApiConfig {
203 &self.config
204 }
205
206 pub fn router(&self) -> Router {
208 self.router.clone()
209 }
210
211 pub fn run_until_shutdown(&self, shutdown: Arc<AtomicBool>) -> io::Result<()> {
213 if !self.config.enabled {
214 return Ok(());
215 }
216 let address = format!("{}:{}", self.config.host, self.config.port);
217 let router = self.router();
218 let runtime = tokio::runtime::Builder::new_current_thread()
219 .enable_all()
220 .build()
221 .map_err(io::Error::other)?;
222 runtime.block_on(async move {
223 let listener = tokio::net::TcpListener::bind(address).await?;
224 axum::serve(listener, router)
225 .with_graceful_shutdown(wait_for_shutdown(shutdown))
226 .await
227 })
228 }
229}
230
231async fn update_required_handler() -> (StatusCode, &'static str) {
232 (
233 StatusCode::UPGRADE_REQUIRED,
234 "NO MORE SUPPORTED PLEASE UPDATE",
235 )
236}
237
238#[derive(Default)]
239pub struct RuntimeHttpStateParts {
241 pub controller: Option<Arc<Mutex<RuntimeController>>>,
243 pub sync_log: Option<Arc<dyn SyncLogView>>,
245 pub tick_counter: Option<Arc<AtomicU64>>,
247 pub app_query_router: Option<Arc<Mutex<ApiRouter>>>,
249 pub operation_mode: Option<Arc<Mutex<RuntimeOperationalMode>>>,
251 pub command_policy: Option<Arc<dyn CommandCapabilityPolicy>>,
253 pub supervisor: Option<appcore_supervisor::Supervisor>,
255 pub auth: HttpCommandAuth,
257}
258
259async fn wait_for_shutdown(shutdown: Arc<AtomicBool>) {
260 loop {
261 if shutdown.load(Ordering::SeqCst) {
262 break;
263 }
264 tokio::time::sleep(Duration::from_millis(100)).await;
265 }
266}
267
268#[cfg(test)]
269mod http_tests;
270#[cfg(test)]
271mod reload_tests;