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