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,
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 let state = HttpState {
161 static_info,
162 controller: parts.controller,
163 app_query_router: parts.app_query_router,
164 sync_log: parts.sync_log,
165 tick_counter: parts.tick_counter,
166 operation_mode: parts.operation_mode,
167 command_policy: parts.command_policy,
168 supervisor: parts.supervisor,
169 auth: parts.auth,
170 max_payload_bytes: config.max_payload_bytes,
171 clock: Arc::new(appcore_core::SystemClock::new()),
172 };
173 let router = Router::new()
174 .route("/v1/health", get(health_handler))
175 .route("/v1/status", get(status_handler))
176 .route("/v1/status/public", get(public_status_handler))
177 .route("/v1/status/private", get(private_status_handler))
178 .route("/v1/diagnostics", get(diagnostics_handler))
179 .route("/v1/command", post(command_handler))
180 .route("/v1/query", post(query_handler))
181 .route("/health", get(update_required_handler))
182 .route("/status", get(update_required_handler))
183 .route("/status/public", get(update_required_handler))
184 .route("/status/private", get(update_required_handler))
185 .route("/diagnostics", get(update_required_handler))
186 .route("/command", post(update_required_handler))
187 .route("/query", post(update_required_handler))
188 .layer(DefaultBodyLimit::max(config.max_payload_bytes))
189 .with_state(state);
190 Self { config, router }
191 }
192
193 pub fn config(&self) -> &HttpApiConfig {
195 &self.config
196 }
197
198 pub fn router(&self) -> Router {
200 self.router.clone()
201 }
202
203 pub fn run_until_shutdown(&self, shutdown: Arc<AtomicBool>) -> io::Result<()> {
205 if !self.config.enabled {
206 return Ok(());
207 }
208 let address = format!("{}:{}", self.config.host, self.config.port);
209 let router = self.router();
210 let runtime = tokio::runtime::Builder::new_current_thread()
211 .enable_all()
212 .build()
213 .map_err(io::Error::other)?;
214 runtime.block_on(async move {
215 let listener = tokio::net::TcpListener::bind(address).await?;
216 axum::serve(listener, router)
217 .with_graceful_shutdown(wait_for_shutdown(shutdown))
218 .await
219 })
220 }
221}
222
223async fn update_required_handler() -> (StatusCode, &'static str) {
224 (
225 StatusCode::UPGRADE_REQUIRED,
226 "NO MORE SUPPORTED PLEASE UPDATE",
227 )
228}
229
230#[derive(Default)]
231pub struct RuntimeHttpStateParts {
233 pub controller: Option<Arc<Mutex<RuntimeController>>>,
235 pub sync_log: Option<Arc<dyn SyncLogView>>,
237 pub tick_counter: Option<Arc<AtomicU64>>,
239 pub app_query_router: Option<Arc<Mutex<ApiRouter>>>,
241 pub operation_mode: Option<Arc<Mutex<RuntimeOperationalMode>>>,
243 pub command_policy: Option<Arc<dyn CommandCapabilityPolicy>>,
245 pub supervisor: Option<appcore_supervisor::Supervisor>,
247 pub auth: HttpCommandAuth,
249}
250
251async fn wait_for_shutdown(shutdown: Arc<AtomicBool>) {
252 loop {
253 if shutdown.load(Ordering::SeqCst) {
254 break;
255 }
256 tokio::time::sleep(Duration::from_millis(100)).await;
257 }
258}
259
260#[cfg(test)]
261mod http_tests;