infinity_bridge_host/
server.rs1use std::sync::Arc;
2use std::time::Duration;
3
4use axum::Router;
5use axum::extract::State;
6use axum::extract::ws::{Message, WebSocket, WebSocketUpgrade};
7use axum::response::IntoResponse;
8use axum::routing::get;
9use futures::{SinkExt, StreamExt};
10use infinity_bridge_wire::{BridgeError, EventPayload, WireMsg};
11use serde_json::Value;
12use tokio::sync::{broadcast, mpsc, watch};
13
14use crate::hub::{ClientInfo, Hub};
15
16pub struct ServerConfig {
17 pub bind_addr: String,
18 pub ws_path: String,
19 pub event_capacity: usize,
20 pub ping_interval: Duration,
21 pub ping_timeout: Duration,
22}
23
24impl ServerConfig {
25 pub fn new(bind_addr: impl Into<String>, ws_path: impl Into<String>) -> Self {
26 Self {
27 bind_addr: bind_addr.into(),
28 ws_path: ws_path.into(),
29 event_capacity: 256,
30 ping_interval: Duration::from_secs(5),
31 ping_timeout: Duration::from_secs(15),
32 }
33 }
34}
35
36#[derive(Clone)]
37pub struct BridgeServer {
38 hub: Arc<Hub>,
39 config: Arc<ServerConfig>,
40}
41
42impl BridgeServer {
43 pub async fn start(config: ServerConfig) -> Result<Self, BridgeError> {
49 let hub = Hub::new(config.event_capacity);
50 let config = Arc::new(config);
51
52 let server = Self {
53 hub: Arc::clone(&hub),
54 config: Arc::clone(&config),
55 };
56
57 let app_state = AppState {
58 hub: Arc::clone(&hub),
59 config: Arc::clone(&config),
60 };
61
62 let app = Router::new()
63 .route(&config.ws_path, get(ws_upgrade))
64 .route("/health", get(health))
65 .with_state(app_state);
66
67 let listener = tokio::net::TcpListener::bind(&config.bind_addr)
68 .await
69 .map_err(|e| BridgeError::transport(format!("bind failed: {e}")))?;
70
71 tokio::spawn(async move {
72 if let Err(e) = axum::serve(listener, app).await {
73 eprintln!("[infinity-bridge-host] Server error: {e}");
74 }
75 });
76
77 Ok(server)
78 }
79
80 pub fn router(config: ServerConfig) -> (Self, Router) {
85 let hub = Hub::new(config.event_capacity);
86 let config = Arc::new(config);
87
88 let server = Self {
89 hub: Arc::clone(&hub),
90 config: Arc::clone(&config),
91 };
92
93 let app_state = AppState {
94 hub: Arc::clone(&hub),
95 config: Arc::clone(&config),
96 };
97
98 let router = Router::new()
99 .route(&config.ws_path, get(ws_upgrade))
100 .route("/health", get(health))
101 .with_state(app_state);
102
103 (server, router)
104 }
105
106 pub async fn command(
110 &self,
111 name: &str,
112 payload: Value,
113 timeout: Duration,
114 ) -> Result<Value, BridgeError> {
115 self.hub.command(Some(name), payload, timeout).await
116 }
117
118 pub async fn command_raw(
120 &self,
121 payload: Value,
122 timeout: Duration,
123 ) -> Result<Value, BridgeError> {
124 self.hub.command(None, payload, timeout).await
125 }
126
127 pub async fn emit(&self, name: impl Into<String>, data: Value) -> Result<(), BridgeError> {
134 self.hub.emit(name, data).await
135 }
136
137 pub fn subscribe_events(&self) -> broadcast::Receiver<EventPayload> {
143 self.hub.subscribe_events()
144 }
145
146 pub async fn is_connected(&self) -> bool {
150 self.hub.is_connected().await
151 }
152
153 pub async fn wait_connected(&self) {
155 self.hub.wait_connected().await
156 }
157
158 pub fn connection_status(&self) -> watch::Receiver<bool> {
163 self.hub.subscribe_connection_status()
164 }
165
166 pub async fn clients(&self) -> Vec<ClientInfo> {
168 self.hub.connected_clients().await
169 }
170}
171
172#[derive(Clone)]
173struct AppState {
174 hub: Arc<Hub>,
175 config: Arc<ServerConfig>,
176}
177
178async fn health() -> impl IntoResponse {
179 "ok"
180}
181
182async fn ws_upgrade(ws: WebSocketUpgrade, State(state): State<AppState>) -> impl IntoResponse {
183 ws.on_upgrade(move |socket| handle_socket(socket, state))
184}
185
186async fn handle_socket(socket: WebSocket, state: AppState) {
187 let (mut ws_tx, mut ws_rx) = socket.split();
188 let (out_tx, mut out_rx) = mpsc::unbounded_channel::<String>();
189
190 let client_id = state.hub.register_client(out_tx.clone()).await;
191
192 let writer = tokio::spawn(async move {
193 while let Some(text) = out_rx.recv().await {
194 if ws_tx.send(Message::Text(text.into())).await.is_err() {
195 break;
196 }
197 }
198 });
199
200 let hub_for_ping = Arc::clone(&state.hub);
201 let ping_interval = state.config.ping_interval;
202 let ping_timeout = state.config.ping_timeout;
203 let ping_task = tokio::spawn(async move {
204 let mut interval = tokio::time::interval(ping_interval);
205 interval.tick().await;
206 loop {
207 interval.tick().await;
208
209 {
210 let dead = hub_for_ping.reap_dead_clients(ping_timeout).await;
211 if dead.contains(&client_id) {
212 break;
213 }
214 }
215
216 let ping = WireMsg::Ping {
217 ts: Some(
218 std::time::SystemTime::now()
219 .duration_since(std::time::UNIX_EPOCH)
220 .unwrap_or_default()
221 .as_millis() as u64,
222 ),
223 };
224 let json = match ping.to_json() {
225 Ok(j) => j,
226 Err(_) => continue,
227 };
228 if hub_for_ping.send_to(client_id, json).await.is_err() {
229 break;
230 }
231 }
232 });
233
234 while let Some(Ok(msg)) = ws_rx.next().await {
235 match msg {
236 Message::Text(text) => {
237 state.hub.touch_client(client_id).await;
238
239 let wire = match WireMsg::from_json(&text) {
240 Ok(w) => w,
241 Err(_) => continue,
242 };
243
244 match wire {
245 WireMsg::Hello(hello) => {
246 state.hub.set_client_hello(client_id, hello).await;
247 }
248 WireMsg::Ack(ack) => {
249 state.hub.dispatch_ack(ack).await;
250 }
251 WireMsg::Event(event) => {
252 state.hub.dispatch_event(event);
253 }
254 WireMsg::Pong { .. } => {
255 }
257 WireMsg::Cmd(cmd) => {
258 state.hub.dispatch_event(EventPayload::new(
259 cmd.name.unwrap_or_else(|| "cmd".into()),
260 cmd.payload,
261 ));
262 }
263 WireMsg::Ping { ts } => {
264 let pong = WireMsg::Pong { ts };
265 if let Ok(json) = pong.to_json() {
266 let _ = state.hub.send_to(client_id, json).await;
267 }
268 }
269 }
270 }
271 Message::Close(_) => break,
272 _ => {}
273 }
274 }
275
276 state.hub.unregister_client(client_id).await;
277 ping_task.abort();
278 drop(out_tx);
279 let _ = writer.await;
280}