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 {
29 bind_addr: Option<String>,
30
31 admin_bind_addr: Option<String>,
32
33 actual_addr: RwLock<Option<SocketAddr>>,
34
35 admin_actual_addr: RwLock<Option<SocketAddr>>,
36
37 state: AppState,
38
39 running: Arc<AtomicBool>,
40
41 shutdown_tx: Option<oneshot::Sender<()>>,
42
43 shutdown_complete_rx: Option<oneshot::Receiver<()>>,
44
45 admin_shutdown_tx: Option<oneshot::Sender<()>>,
46
47 admin_shutdown_complete_rx: Option<oneshot::Receiver<()>>,
48
49 runtime: SharedRuntime,
50}
51
52impl HttpSubsystem {
53 pub fn new(
54 bind_addr: Option<String>,
55 admin_bind_addr: Option<String>,
56 state: AppState,
57 runtime: SharedRuntime,
58 ) -> Self {
59 Self {
60 bind_addr,
61 admin_bind_addr,
62 actual_addr: RwLock::new(None),
63 admin_actual_addr: RwLock::new(None),
64 state,
65 running: Arc::new(AtomicBool::new(false)),
66 shutdown_tx: None,
67 shutdown_complete_rx: None,
68 admin_shutdown_tx: None,
69 admin_shutdown_complete_rx: None,
70 runtime,
71 }
72 }
73
74 pub fn bind_addr(&self) -> Option<&str> {
75 self.bind_addr.as_deref()
76 }
77
78 pub fn local_addr(&self) -> Option<SocketAddr> {
79 *self.actual_addr.read().unwrap()
80 }
81
82 pub fn port(&self) -> Option<u16> {
83 self.local_addr().map(|a| a.port())
84 }
85
86 pub fn admin_local_addr(&self) -> Option<SocketAddr> {
87 *self.admin_actual_addr.read().unwrap()
88 }
89
90 pub fn admin_port(&self) -> Option<u16> {
91 self.admin_local_addr().map(|a| a.port())
92 }
93
94 fn spawn_main_server(&mut self) -> Result<()> {
95 let Some(addr) = self.bind_addr.clone() else {
96 self.running.store(true, Ordering::SeqCst);
97 return Ok(());
98 };
99 let listener = self.bind_listener(&addr)?;
100 let actual_addr = local_addr_or_err(&listener)?;
101 *self.actual_addr.write().unwrap() = Some(actual_addr);
102 info!("HTTP server bound to {}", actual_addr);
103
104 let (shutdown_tx, shutdown_rx) = oneshot::channel();
105 let (complete_tx, complete_rx) = oneshot::channel();
106 let server_state = HttpServerState::new(self.state.clone());
107 let running = self.running.clone();
108 self.runtime.spawn(async move {
109 running.store(true, Ordering::SeqCst);
110 let result = serve_http(listener, server_state, shutdown_rx, "HTTP server").await;
111 if let Err(e) = result {
112 error!("HTTP server error: {}", e);
113 }
114 running.store(false, Ordering::SeqCst);
115 let _ = complete_tx.send(());
116 info!("HTTP server stopped");
117 });
118 self.shutdown_tx = Some(shutdown_tx);
119 self.shutdown_complete_rx = Some(complete_rx);
120 Ok(())
121 }
122
123 fn spawn_admin_server(&mut self) -> Result<()> {
124 let Some(admin_addr) = self.admin_bind_addr.clone() else {
125 return Ok(());
126 };
127 let listener = self.bind_listener(&admin_addr)?;
128 let actual_addr = local_addr_or_err(&listener)?;
129 *self.admin_actual_addr.write().unwrap() = Some(actual_addr);
130 info!("HTTP admin server bound to {}", actual_addr);
131
132 let (admin_shutdown_tx, admin_shutdown_rx) = oneshot::channel();
133 let (admin_complete_tx, admin_complete_rx) = oneshot::channel();
134 let admin_config = self.state.config().clone().admin_enabled(true);
135 let admin_app_state = self.state.clone_with_config(admin_config);
136 let admin_server_state = HttpServerState::new(admin_app_state);
137 self.runtime.spawn(async move {
138 let result =
139 serve_http(listener, admin_server_state, admin_shutdown_rx, "HTTP admin server").await;
140 if let Err(e) = result {
141 error!("HTTP admin server error: {}", e);
142 }
143 let _ = admin_complete_tx.send(());
144 info!("HTTP admin server stopped");
145 });
146 self.admin_shutdown_tx = Some(admin_shutdown_tx);
147 self.admin_shutdown_complete_rx = Some(admin_complete_rx);
148 Ok(())
149 }
150
151 #[inline]
152 fn bind_listener(&self, addr: &str) -> Result<TcpListener> {
153 self.runtime.block_on(TcpListener::bind(addr)).map_err(|e| {
154 CoreError::SubsystemBindFailed {
155 addr: addr.to_string(),
156 reason: e.to_string(),
157 }
158 .into()
159 })
160 }
161}
162
163impl HasVersion for HttpSubsystem {
164 fn version(&self) -> SystemVersion {
165 SystemVersion {
166 name: env!("CARGO_PKG_NAME")
167 .strip_prefix("reifydb-")
168 .unwrap_or(env!("CARGO_PKG_NAME"))
169 .to_string(),
170 version: env!("CARGO_PKG_VERSION").to_string(),
171 description: "HTTP server subsystem for query and command handling".to_string(),
172 r#type: ComponentType::Subsystem,
173 }
174 }
175}
176
177impl Subsystem for HttpSubsystem {
178 fn name(&self) -> &'static str {
179 "Http"
180 }
181
182 fn start(&mut self) -> Result<()> {
183 if self.running.load(Ordering::SeqCst) {
184 return Ok(());
185 }
186 self.spawn_main_server()?;
187 self.spawn_admin_server()?;
188 Ok(())
189 }
190
191 fn shutdown(&mut self) -> Result<()> {
192 if let Some(tx) = self.admin_shutdown_tx.take() {
193 let _ = tx.send(());
194 }
195 if let Some(rx) = self.admin_shutdown_complete_rx.take() {
196 let _ = self.runtime.block_on(rx);
197 }
198
199 if let Some(tx) = self.shutdown_tx.take() {
200 let _ = tx.send(());
201 }
202 if let Some(rx) = self.shutdown_complete_rx.take() {
203 let _ = self.runtime.block_on(rx);
204 }
205 self.running.store(false, Ordering::SeqCst);
206 Ok(())
207 }
208
209 fn is_running(&self) -> bool {
210 self.running.load(Ordering::SeqCst)
211 }
212
213 fn health_status(&self) -> HealthStatus {
214 if self.running.load(Ordering::SeqCst) {
215 HealthStatus::Healthy
216 } else if self.shutdown_tx.is_some() {
217 HealthStatus::Warning {
218 description: "Starting up".to_string(),
219 }
220 } else {
221 HealthStatus::Failed {
222 description: "Not running".to_string(),
223 }
224 }
225 }
226
227 fn as_any(&self) -> &dyn Any {
228 self
229 }
230
231 fn as_any_mut(&mut self) -> &mut dyn Any {
232 self
233 }
234}
235
236#[inline]
237fn local_addr_or_err(listener: &TcpListener) -> Result<SocketAddr> {
238 listener.local_addr().map_err(|e| {
239 CoreError::SubsystemAddressUnavailable {
240 reason: e.to_string(),
241 }
242 .into()
243 })
244}
245
246async fn serve_http(
247 listener: TcpListener,
248 server_state: HttpServerState,
249 shutdown_rx: oneshot::Receiver<()>,
250 name: &'static str,
251) -> io::Result<()> {
252 let app = router(server_state);
253 let listener = listener.tap_io(|tcp_stream| {
254 if let Err(e) = tcp_stream.set_nodelay(true) {
255 warn!("Failed to set TCP_NODELAY: {e}");
256 }
257 });
258 serve(listener, app)
259 .with_graceful_shutdown(async move {
260 shutdown_rx.await.ok();
261 info!("{} received shutdown signal", name);
262 })
263 .await
264}