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, ShutdownErrors},
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::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<io::Result<()>>>>,
54
55	admin_shutdown_tx: Mutex<Option<oneshot::Sender<()>>>,
56
57	admin_shutdown_complete_rx: Mutex<Option<oneshot::Receiver<io::Result<()>>>>,
58
59	connection_semaphore: Arc<Semaphore>,
60
61	handle: Handle,
62}
63
64type ShutdownHandles = (oneshot::Sender<()>, oneshot::Receiver<io::Result<()>>);
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 served = serve_http(listener, server_state, semaphore, shutdown_rx, "HTTP server").await;
145			running.store(false, Ordering::SeqCst);
146			let _ = complete_tx.send(served);
147			info!("HTTP server stopped");
148		});
149		(shutdown_tx, complete_rx)
150	}
151
152	#[inline]
153	fn store_main_shutdown_handles(
154		&self,
155		shutdown_tx: oneshot::Sender<()>,
156		complete_rx: oneshot::Receiver<io::Result<()>>,
157	) {
158		*self.shutdown_tx.lock() = Some(shutdown_tx);
159		*self.shutdown_complete_rx.lock() = Some(complete_rx);
160	}
161
162	fn spawn_admin_server(&self) -> Result<()> {
163		let Some(listener) = self.bind_admin_listener()? else {
164			return Ok(());
165		};
166		let (shutdown_tx, complete_rx) = self.spawn_admin_serve_task(listener);
167		self.store_admin_shutdown_handles(shutdown_tx, complete_rx);
168		Ok(())
169	}
170
171	#[inline]
172	fn bind_admin_listener(&self) -> Result<Option<TcpListener>> {
173		let Some(admin_addr) = self.admin_bind_addr.clone() else {
174			return Ok(None);
175		};
176		let listener = self.bind_listener(&admin_addr)?;
177		let actual_addr = local_addr_or_err(&listener)?;
178		*self.admin_actual_addr.write() = Some(actual_addr);
179		info!("HTTP admin server bound to {}", actual_addr);
180		Ok(Some(listener))
181	}
182
183	#[inline]
184	fn spawn_admin_serve_task(&self, listener: TcpListener) -> ShutdownHandles {
185		let (admin_shutdown_tx, admin_shutdown_rx) = oneshot::channel();
186		let (admin_complete_tx, admin_complete_rx) = oneshot::channel();
187		let admin_config = self.state.config().clone().admin_enabled(true);
188		let admin_app_state = self.state.clone_with_config(admin_config);
189		let admin_server_state = HttpServerState::new(admin_app_state);
190		let semaphore = self.connection_semaphore.clone();
191		self.handle.spawn(async move {
192			let served = serve_http(
193				listener,
194				admin_server_state,
195				semaphore,
196				admin_shutdown_rx,
197				"HTTP admin server",
198			)
199			.await;
200			let _ = admin_complete_tx.send(served);
201			info!("HTTP admin server stopped");
202		});
203		(admin_shutdown_tx, admin_complete_rx)
204	}
205
206	#[inline]
207	fn store_admin_shutdown_handles(
208		&self,
209		shutdown_tx: oneshot::Sender<()>,
210		complete_rx: oneshot::Receiver<io::Result<()>>,
211	) {
212		*self.admin_shutdown_tx.lock() = Some(shutdown_tx);
213		*self.admin_shutdown_complete_rx.lock() = Some(complete_rx);
214	}
215
216	#[inline]
217	fn stop_admin_server(&self, errors: &mut ShutdownErrors) {
218		let admin_tx = self.admin_shutdown_tx.lock().take();
219		if let Some(tx) = admin_tx {
220			let _ = tx.send(());
221		}
222		let admin_rx = self.admin_shutdown_complete_rx.lock().take();
223		if let Some(rx) = admin_rx {
224			match self.handle.block_on(rx) {
225				Ok(Ok(())) => {}
226				Ok(Err(err)) => errors.fail("admin server", err),
227				Err(err) => errors.fail("admin server", err),
228			}
229		}
230	}
231
232	#[inline]
233	fn stop_main_server(&self, errors: &mut ShutdownErrors) {
234		let main_tx = self.shutdown_tx.lock().take();
235		if let Some(tx) = main_tx {
236			let _ = tx.send(());
237		}
238		let main_rx = self.shutdown_complete_rx.lock().take();
239		if let Some(rx) = main_rx {
240			match self.handle.block_on(rx) {
241				Ok(Ok(())) => {}
242				Ok(Err(err)) => errors.fail("main server", err),
243				Err(err) => errors.fail("main server", err),
244			}
245		}
246	}
247
248	#[inline]
249	fn bind_listener(&self, addr: &str) -> Result<TcpListener> {
250		self.handle.block_on(TcpListener::bind(addr)).map_err(|e| {
251			CoreError::SubsystemBindFailed {
252				addr: addr.to_string(),
253				reason: e.to_string(),
254			}
255			.into()
256		})
257	}
258}
259
260impl HasVersion for HttpSubsystem {
261	fn version(&self) -> SystemVersion {
262		SystemVersion {
263			name: env!("CARGO_PKG_NAME")
264				.strip_prefix("reifydb-")
265				.unwrap_or(env!("CARGO_PKG_NAME"))
266				.to_string(),
267			version: env!("CARGO_PKG_VERSION").to_string(),
268			description: "HTTP server subsystem for query and command handling".to_string(),
269			r#type: ComponentType::Subsystem,
270		}
271	}
272}
273
274impl Shutdown for HttpSubsystem {
275	fn shutdown(&self) -> Result<()> {
276		let mut errors = ShutdownErrors::new("http");
277		self.stop_admin_server(&mut errors);
278		self.stop_main_server(&mut errors);
279		self.running.store(false, Ordering::SeqCst);
280		errors.finish()
281	}
282}
283
284impl Subsystem for HttpSubsystem {
285	fn name(&self) -> &'static str {
286		"http"
287	}
288
289	fn is_running(&self) -> bool {
290		self.running.load(Ordering::SeqCst)
291	}
292
293	fn health_status(&self) -> HealthStatus {
294		if self.running.load(Ordering::SeqCst) {
295			HealthStatus::Healthy
296		} else if self.shutdown_tx.lock().is_some() {
297			HealthStatus::Warning {
298				description: "Starting up".to_string(),
299			}
300		} else {
301			HealthStatus::Failed {
302				description: "Not running".to_string(),
303			}
304		}
305	}
306
307	fn as_any(&self) -> &dyn Any {
308		self
309	}
310}
311
312#[inline]
313fn local_addr_or_err(listener: &TcpListener) -> Result<SocketAddr> {
314	listener.local_addr().map_err(|e| {
315		CoreError::SubsystemAddressUnavailable {
316			reason: e.to_string(),
317		}
318		.into()
319	})
320}
321
322async fn serve_http(
323	listener: TcpListener,
324	server_state: HttpServerState,
325	semaphore: Arc<Semaphore>,
326	shutdown_rx: oneshot::Receiver<()>,
327	name: &'static str,
328) -> io::Result<()> {
329	let app = router(server_state);
330	let listener = LimitedListener::new(listener, semaphore, name);
331	serve(listener, app)
332		.with_graceful_shutdown(async move {
333			shutdown_rx.await.ok();
334			info!("{} received shutdown signal", name);
335		})
336		.await
337}
338
339pub struct LimitedListener {
340	listener: TcpListener,
341	semaphore: Arc<Semaphore>,
342	name: &'static str,
343}
344
345impl LimitedListener {
346	pub fn new(listener: TcpListener, semaphore: Arc<Semaphore>, name: &'static str) -> Self {
347		Self {
348			listener,
349			semaphore,
350			name,
351		}
352	}
353}
354
355impl Listener for LimitedListener {
356	type Io = PermittedStream;
357	type Addr = SocketAddr;
358
359	async fn accept(&mut self) -> (Self::Io, Self::Addr) {
360		let (stream, permit, peer) = accept_admitted(&self.listener, &self.semaphore, self.name).await;
361		(PermittedStream::new(stream, permit), peer)
362	}
363
364	fn local_addr(&self) -> io::Result<Self::Addr> {
365		self.listener.local_addr()
366	}
367}