reifydb_sub_server_http/
subsystem.rs1use std::{
5 any::Any,
6 io,
7 net::SocketAddr,
8 sync::{
9 Arc, RwLock,
10 atomic::{AtomicBool, Ordering},
11 },
12};
13
14use axum::{serve, serve::ListenerExt};
15use reifydb_core::{
16 error::CoreError,
17 interface::version::{ComponentType, HasVersion, SystemVersion},
18};
19use reifydb_runtime::SharedRuntime;
20use reifydb_sub_api::subsystem::{HealthStatus, Subsystem};
21use reifydb_sub_server::state::AppState;
22use reifydb_type::Result;
23use tokio::{net::TcpListener, sync::oneshot};
24use tracing::{error, info, warn};
25
26use crate::{routes::router, state::HttpServerState};
27
28pub struct HttpSubsystem {
51 bind_addr: Option<String>,
53 admin_bind_addr: Option<String>,
55 actual_addr: RwLock<Option<SocketAddr>>,
57 admin_actual_addr: RwLock<Option<SocketAddr>>,
59 state: AppState,
61 running: Arc<AtomicBool>,
63 shutdown_tx: Option<oneshot::Sender<()>>,
65 shutdown_complete_rx: Option<oneshot::Receiver<()>>,
67 admin_shutdown_tx: Option<oneshot::Sender<()>>,
69 admin_shutdown_complete_rx: Option<oneshot::Receiver<()>>,
71 runtime: SharedRuntime,
73}
74
75impl HttpSubsystem {
76 pub fn new(
84 bind_addr: Option<String>,
85 admin_bind_addr: Option<String>,
86 state: AppState,
87 runtime: SharedRuntime,
88 ) -> Self {
89 Self {
90 bind_addr,
91 admin_bind_addr,
92 actual_addr: RwLock::new(None),
93 admin_actual_addr: RwLock::new(None),
94 state,
95 running: Arc::new(AtomicBool::new(false)),
96 shutdown_tx: None,
97 shutdown_complete_rx: None,
98 admin_shutdown_tx: None,
99 admin_shutdown_complete_rx: None,
100 runtime,
101 }
102 }
103
104 pub fn bind_addr(&self) -> Option<&str> {
106 self.bind_addr.as_deref()
107 }
108
109 pub fn local_addr(&self) -> Option<SocketAddr> {
111 *self.actual_addr.read().unwrap()
112 }
113
114 pub fn port(&self) -> Option<u16> {
116 self.local_addr().map(|a| a.port())
117 }
118
119 pub fn admin_local_addr(&self) -> Option<SocketAddr> {
121 *self.admin_actual_addr.read().unwrap()
122 }
123
124 pub fn admin_port(&self) -> Option<u16> {
126 self.admin_local_addr().map(|a| a.port())
127 }
128
129 fn spawn_main_server(&mut self) -> Result<()> {
130 let Some(addr) = self.bind_addr.clone() else {
131 self.running.store(true, Ordering::SeqCst);
132 return Ok(());
133 };
134 let listener = self.bind_listener(&addr)?;
135 let actual_addr = local_addr_or_err(&listener)?;
136 *self.actual_addr.write().unwrap() = Some(actual_addr);
137 info!("HTTP server bound to {}", actual_addr);
138
139 let (shutdown_tx, shutdown_rx) = oneshot::channel();
140 let (complete_tx, complete_rx) = oneshot::channel();
141 let server_state = HttpServerState::new(self.state.clone());
142 let running = self.running.clone();
143 self.runtime.spawn(async move {
144 running.store(true, Ordering::SeqCst);
145 let result = serve_http(listener, server_state, shutdown_rx, "HTTP server").await;
146 if let Err(e) = result {
147 error!("HTTP server error: {}", e);
148 }
149 running.store(false, Ordering::SeqCst);
150 let _ = complete_tx.send(());
151 info!("HTTP server stopped");
152 });
153 self.shutdown_tx = Some(shutdown_tx);
154 self.shutdown_complete_rx = Some(complete_rx);
155 Ok(())
156 }
157
158 fn spawn_admin_server(&mut self) -> Result<()> {
159 let Some(admin_addr) = self.admin_bind_addr.clone() else {
160 return Ok(());
161 };
162 let listener = self.bind_listener(&admin_addr)?;
163 let actual_addr = local_addr_or_err(&listener)?;
164 *self.admin_actual_addr.write().unwrap() = Some(actual_addr);
165 info!("HTTP admin server bound to {}", actual_addr);
166
167 let (admin_shutdown_tx, admin_shutdown_rx) = oneshot::channel();
168 let (admin_complete_tx, admin_complete_rx) = oneshot::channel();
169 let admin_config = self.state.config().clone().admin_enabled(true);
170 let admin_app_state = self.state.clone_with_config(admin_config);
171 let admin_server_state = HttpServerState::new(admin_app_state);
172 self.runtime.spawn(async move {
173 let result =
174 serve_http(listener, admin_server_state, admin_shutdown_rx, "HTTP admin server").await;
175 if let Err(e) = result {
176 error!("HTTP admin server error: {}", e);
177 }
178 let _ = admin_complete_tx.send(());
179 info!("HTTP admin server stopped");
180 });
181 self.admin_shutdown_tx = Some(admin_shutdown_tx);
182 self.admin_shutdown_complete_rx = Some(admin_complete_rx);
183 Ok(())
184 }
185
186 #[inline]
187 fn bind_listener(&self, addr: &str) -> Result<TcpListener> {
188 self.runtime.block_on(TcpListener::bind(addr)).map_err(|e| {
189 CoreError::SubsystemBindFailed {
190 addr: addr.to_string(),
191 reason: e.to_string(),
192 }
193 .into()
194 })
195 }
196}
197
198impl HasVersion for HttpSubsystem {
199 fn version(&self) -> SystemVersion {
200 SystemVersion {
201 name: env!("CARGO_PKG_NAME")
202 .strip_prefix("reifydb-")
203 .unwrap_or(env!("CARGO_PKG_NAME"))
204 .to_string(),
205 version: env!("CARGO_PKG_VERSION").to_string(),
206 description: "HTTP server subsystem for query and command handling".to_string(),
207 r#type: ComponentType::Subsystem,
208 }
209 }
210}
211
212impl Subsystem for HttpSubsystem {
213 fn name(&self) -> &'static str {
214 "Http"
215 }
216
217 fn start(&mut self) -> Result<()> {
218 if self.running.load(Ordering::SeqCst) {
219 return Ok(());
220 }
221 self.spawn_main_server()?;
222 self.spawn_admin_server()?;
223 Ok(())
224 }
225
226 fn shutdown(&mut self) -> Result<()> {
227 if let Some(tx) = self.admin_shutdown_tx.take() {
229 let _ = tx.send(());
230 }
231 if let Some(rx) = self.admin_shutdown_complete_rx.take() {
232 let _ = self.runtime.block_on(rx);
233 }
234 if let Some(tx) = self.shutdown_tx.take() {
236 let _ = tx.send(());
237 }
238 if let Some(rx) = self.shutdown_complete_rx.take() {
239 let _ = self.runtime.block_on(rx);
240 }
241 self.running.store(false, Ordering::SeqCst);
242 Ok(())
243 }
244
245 fn is_running(&self) -> bool {
246 self.running.load(Ordering::SeqCst)
247 }
248
249 fn health_status(&self) -> HealthStatus {
250 if self.running.load(Ordering::SeqCst) {
251 HealthStatus::Healthy
252 } else if self.shutdown_tx.is_some() {
253 HealthStatus::Warning {
255 description: "Starting up".to_string(),
256 }
257 } else {
258 HealthStatus::Failed {
259 description: "Not running".to_string(),
260 }
261 }
262 }
263
264 fn as_any(&self) -> &dyn Any {
265 self
266 }
267
268 fn as_any_mut(&mut self) -> &mut dyn Any {
269 self
270 }
271}
272
273#[inline]
274fn local_addr_or_err(listener: &TcpListener) -> Result<SocketAddr> {
275 listener.local_addr().map_err(|e| {
276 CoreError::SubsystemAddressUnavailable {
277 reason: e.to_string(),
278 }
279 .into()
280 })
281}
282
283async fn serve_http(
284 listener: TcpListener,
285 server_state: HttpServerState,
286 shutdown_rx: oneshot::Receiver<()>,
287 name: &'static str,
288) -> io::Result<()> {
289 let app = router(server_state);
290 let listener = listener.tap_io(|tcp_stream| {
291 if let Err(e) = tcp_stream.set_nodelay(true) {
292 warn!("Failed to set TCP_NODELAY: {e}");
293 }
294 });
295 serve(listener, app)
296 .with_graceful_shutdown(async move {
297 shutdown_rx.await.ok();
298 info!("{} received shutdown signal", name);
299 })
300 .await
301}