Skip to main content

reifydb_sub_server_http/
subsystem.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use std::{
5	any::Any,
6	io,
7	net::SocketAddr,
8	sync::{
9		Arc,
10		atomic::{AtomicBool, Ordering},
11	},
12};
13
14use axum::{serve, serve::Listener};
15use reifydb_core::{
16	error::CoreError,
17	interface::version::{ComponentType, HasVersion, SystemVersion},
18};
19use reifydb_runtime::{
20	shutdown::Shutdown,
21	sync::{mutex::Mutex, rwlock::RwLock},
22};
23use reifydb_sub_api::subsystem::{HealthStatus, Subsystem};
24use reifydb_sub_server::{
25	accept::{PermittedStream, accept_admitted},
26	state::AppState,
27};
28use reifydb_value::Result;
29use tokio::{
30	net::TcpListener,
31	runtime::Handle,
32	sync::{Semaphore, oneshot},
33};
34use tracing::{error, info};
35
36use crate::{routes::router, state::HttpServerState};
37
38pub struct HttpSubsystem {
39	bind_addr: Option<String>,
40
41	admin_bind_addr: Option<String>,
42
43	actual_addr: RwLock<Option<SocketAddr>>,
44
45	admin_actual_addr: RwLock<Option<SocketAddr>>,
46
47	state: AppState,
48
49	running: Arc<AtomicBool>,
50
51	shutdown_tx: Mutex<Option<oneshot::Sender<()>>>,
52
53	shutdown_complete_rx: Mutex<Option<oneshot::Receiver<()>>>,
54
55	admin_shutdown_tx: Mutex<Option<oneshot::Sender<()>>>,
56
57	admin_shutdown_complete_rx: Mutex<Option<oneshot::Receiver<()>>>,
58
59	connection_semaphore: Arc<Semaphore>,
60
61	handle: Handle,
62}
63
64type ShutdownHandles = (oneshot::Sender<()>, oneshot::Receiver<()>);
65
66impl HttpSubsystem {
67	pub fn new(
68		bind_addr: Option<String>,
69		admin_bind_addr: Option<String>,
70		state: AppState,
71		handle: Handle,
72	) -> Result<Self> {
73		let connection_semaphore = Arc::new(Semaphore::new(state.max_connections()));
74		let subsystem = Self {
75			bind_addr,
76			admin_bind_addr,
77			actual_addr: RwLock::new(None),
78			admin_actual_addr: RwLock::new(None),
79			state,
80			running: Arc::new(AtomicBool::new(false)),
81			shutdown_tx: Mutex::new(None),
82			shutdown_complete_rx: Mutex::new(None),
83			admin_shutdown_tx: Mutex::new(None),
84			admin_shutdown_complete_rx: Mutex::new(None),
85			connection_semaphore,
86			handle,
87		};
88		subsystem.spawn_main_server()?;
89		subsystem.spawn_admin_server()?;
90		Ok(subsystem)
91	}
92
93	pub fn bind_addr(&self) -> Option<&str> {
94		self.bind_addr.as_deref()
95	}
96
97	pub fn local_addr(&self) -> Option<SocketAddr> {
98		*self.actual_addr.read()
99	}
100
101	pub fn port(&self) -> Option<u16> {
102		self.local_addr().map(|a| a.port())
103	}
104
105	pub fn admin_local_addr(&self) -> Option<SocketAddr> {
106		*self.admin_actual_addr.read()
107	}
108
109	pub fn admin_port(&self) -> Option<u16> {
110		self.admin_local_addr().map(|a| a.port())
111	}
112
113	fn spawn_main_server(&self) -> Result<()> {
114		let Some(listener) = self.bind_main_listener()? else {
115			return Ok(());
116		};
117		let (shutdown_tx, complete_rx) = self.spawn_main_serve_task(listener);
118		self.store_main_shutdown_handles(shutdown_tx, complete_rx);
119		Ok(())
120	}
121
122	#[inline]
123	fn bind_main_listener(&self) -> Result<Option<TcpListener>> {
124		let Some(addr) = self.bind_addr.clone() else {
125			self.running.store(true, Ordering::SeqCst);
126			return Ok(None);
127		};
128		let listener = self.bind_listener(&addr)?;
129		let actual_addr = local_addr_or_err(&listener)?;
130		*self.actual_addr.write() = Some(actual_addr);
131		info!("HTTP server bound to {}", actual_addr);
132		Ok(Some(listener))
133	}
134
135	#[inline]
136	fn spawn_main_serve_task(&self, listener: TcpListener) -> ShutdownHandles {
137		let (shutdown_tx, shutdown_rx) = oneshot::channel();
138		let (complete_tx, complete_rx) = oneshot::channel();
139		let server_state = HttpServerState::new(self.state.clone());
140		let semaphore = self.connection_semaphore.clone();
141		let running = self.running.clone();
142		self.handle.spawn(async move {
143			running.store(true, Ordering::SeqCst);
144			let result = serve_http(listener, server_state, semaphore, shutdown_rx, "HTTP server").await;
145			if let Err(e) = result {
146				error!("HTTP server error: {}", e);
147			}
148			running.store(false, Ordering::SeqCst);
149			let _ = complete_tx.send(());
150			info!("HTTP server stopped");
151		});
152		(shutdown_tx, complete_rx)
153	}
154
155	#[inline]
156	fn store_main_shutdown_handles(&self, shutdown_tx: oneshot::Sender<()>, complete_rx: oneshot::Receiver<()>) {
157		*self.shutdown_tx.lock() = Some(shutdown_tx);
158		*self.shutdown_complete_rx.lock() = Some(complete_rx);
159	}
160
161	fn spawn_admin_server(&self) -> Result<()> {
162		let Some(listener) = self.bind_admin_listener()? else {
163			return Ok(());
164		};
165		let (shutdown_tx, complete_rx) = self.spawn_admin_serve_task(listener);
166		self.store_admin_shutdown_handles(shutdown_tx, complete_rx);
167		Ok(())
168	}
169
170	#[inline]
171	fn bind_admin_listener(&self) -> Result<Option<TcpListener>> {
172		let Some(admin_addr) = self.admin_bind_addr.clone() else {
173			return Ok(None);
174		};
175		let listener = self.bind_listener(&admin_addr)?;
176		let actual_addr = local_addr_or_err(&listener)?;
177		*self.admin_actual_addr.write() = Some(actual_addr);
178		info!("HTTP admin server bound to {}", actual_addr);
179		Ok(Some(listener))
180	}
181
182	#[inline]
183	fn spawn_admin_serve_task(&self, listener: TcpListener) -> ShutdownHandles {
184		let (admin_shutdown_tx, admin_shutdown_rx) = oneshot::channel();
185		let (admin_complete_tx, admin_complete_rx) = oneshot::channel();
186		let admin_config = self.state.config().clone().admin_enabled(true);
187		let admin_app_state = self.state.clone_with_config(admin_config);
188		let admin_server_state = HttpServerState::new(admin_app_state);
189		let semaphore = self.connection_semaphore.clone();
190		self.handle.spawn(async move {
191			let result = serve_http(
192				listener,
193				admin_server_state,
194				semaphore,
195				admin_shutdown_rx,
196				"HTTP admin server",
197			)
198			.await;
199			if let Err(e) = result {
200				error!("HTTP admin server error: {}", e);
201			}
202			let _ = admin_complete_tx.send(());
203			info!("HTTP admin server stopped");
204		});
205		(admin_shutdown_tx, admin_complete_rx)
206	}
207
208	#[inline]
209	fn store_admin_shutdown_handles(&self, shutdown_tx: oneshot::Sender<()>, complete_rx: oneshot::Receiver<()>) {
210		*self.admin_shutdown_tx.lock() = Some(shutdown_tx);
211		*self.admin_shutdown_complete_rx.lock() = Some(complete_rx);
212	}
213
214	#[inline]
215	fn stop_admin_server(&self) {
216		let admin_tx = self.admin_shutdown_tx.lock().take();
217		if let Some(tx) = admin_tx {
218			let _ = tx.send(());
219		}
220		let admin_rx = self.admin_shutdown_complete_rx.lock().take();
221		if let Some(rx) = admin_rx {
222			let _ = self.handle.block_on(rx);
223		}
224	}
225
226	#[inline]
227	fn stop_main_server(&self) {
228		let main_tx = self.shutdown_tx.lock().take();
229		if let Some(tx) = main_tx {
230			let _ = tx.send(());
231		}
232		let main_rx = self.shutdown_complete_rx.lock().take();
233		if let Some(rx) = main_rx {
234			let _ = self.handle.block_on(rx);
235		}
236	}
237
238	#[inline]
239	fn bind_listener(&self, addr: &str) -> Result<TcpListener> {
240		self.handle.block_on(TcpListener::bind(addr)).map_err(|e| {
241			CoreError::SubsystemBindFailed {
242				addr: addr.to_string(),
243				reason: e.to_string(),
244			}
245			.into()
246		})
247	}
248}
249
250impl HasVersion for HttpSubsystem {
251	fn version(&self) -> SystemVersion {
252		SystemVersion {
253			name: env!("CARGO_PKG_NAME")
254				.strip_prefix("reifydb-")
255				.unwrap_or(env!("CARGO_PKG_NAME"))
256				.to_string(),
257			version: env!("CARGO_PKG_VERSION").to_string(),
258			description: "HTTP server subsystem for query and command handling".to_string(),
259			r#type: ComponentType::Subsystem,
260		}
261	}
262}
263
264impl Shutdown for HttpSubsystem {
265	fn shutdown(&self) {
266		self.stop_admin_server();
267		self.stop_main_server();
268		self.running.store(false, Ordering::SeqCst);
269	}
270}
271
272impl Subsystem for HttpSubsystem {
273	fn name(&self) -> &'static str {
274		"http"
275	}
276
277	fn is_running(&self) -> bool {
278		self.running.load(Ordering::SeqCst)
279	}
280
281	fn health_status(&self) -> HealthStatus {
282		if self.running.load(Ordering::SeqCst) {
283			HealthStatus::Healthy
284		} else if self.shutdown_tx.lock().is_some() {
285			HealthStatus::Warning {
286				description: "Starting up".to_string(),
287			}
288		} else {
289			HealthStatus::Failed {
290				description: "Not running".to_string(),
291			}
292		}
293	}
294
295	fn as_any(&self) -> &dyn Any {
296		self
297	}
298}
299
300#[inline]
301fn local_addr_or_err(listener: &TcpListener) -> Result<SocketAddr> {
302	listener.local_addr().map_err(|e| {
303		CoreError::SubsystemAddressUnavailable {
304			reason: e.to_string(),
305		}
306		.into()
307	})
308}
309
310async fn serve_http(
311	listener: TcpListener,
312	server_state: HttpServerState,
313	semaphore: Arc<Semaphore>,
314	shutdown_rx: oneshot::Receiver<()>,
315	name: &'static str,
316) -> io::Result<()> {
317	let app = router(server_state);
318	let listener = LimitedListener::new(listener, semaphore, name);
319	serve(listener, app)
320		.with_graceful_shutdown(async move {
321			shutdown_rx.await.ok();
322			info!("{} received shutdown signal", name);
323		})
324		.await
325}
326
327pub struct LimitedListener {
328	listener: TcpListener,
329	semaphore: Arc<Semaphore>,
330	name: &'static str,
331}
332
333impl LimitedListener {
334	pub fn new(listener: TcpListener, semaphore: Arc<Semaphore>, name: &'static str) -> Self {
335		Self {
336			listener,
337			semaphore,
338			name,
339		}
340	}
341}
342
343impl Listener for LimitedListener {
344	type Io = PermittedStream;
345	type Addr = SocketAddr;
346
347	async fn accept(&mut self) -> (Self::Io, Self::Addr) {
348		let (stream, permit, peer) = accept_admitted(&self.listener, &self.semaphore, self.name).await;
349		(PermittedStream::new(stream, permit), peer)
350	}
351
352	fn local_addr(&self) -> io::Result<Self::Addr> {
353		self.listener.local_addr()
354	}
355}