feat(web-gateway,web-dashboard): WS ping/pong keepalive, vite build config
- Respond to WebSocket Ping frames with Pong for keepalive detection - Refactor WS handler to share sender between broadcast and recv tasks via Arc<Mutex> (required for pong responses from recv task) - Add vite production build config: source maps, manual chunk splitting for vendor/charts/query bundles Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -12,4 +12,16 @@ export default defineConfig({
|
||||
},
|
||||
},
|
||||
},
|
||||
build: {
|
||||
sourcemap: true,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: {
|
||||
vendor: ['react', 'react-dom', 'react-router-dom'],
|
||||
charts: ['lightweight-charts', 'recharts'],
|
||||
query: ['@tanstack/react-query'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -48,7 +48,7 @@ fn validate_ws_token(token: &str, config: &Arc<GatewayConfig>) -> Result<Claims,
|
||||
|
||||
/// Handle an established WebSocket connection
|
||||
async fn handle_ws_connection(socket: WebSocket, state: AppState) {
|
||||
let (mut ws_sender, mut ws_receiver) = socket.split();
|
||||
let (ws_sender, mut ws_receiver) = socket.split();
|
||||
|
||||
// Subscribe to the broadcast channel for server events
|
||||
let mut broadcast_rx = state.ws_broadcast.subscribe();
|
||||
@@ -59,6 +59,11 @@ async fn handle_ws_connection(socket: WebSocket, state: AppState) {
|
||||
|
||||
let topics_for_broadcast = subscribed_topics.clone();
|
||||
|
||||
// Shared sender for both broadcast forwarding and pong responses
|
||||
let sender = Arc::new(tokio::sync::Mutex::new(ws_sender));
|
||||
let sender_for_broadcast = sender.clone();
|
||||
let sender_for_recv = sender.clone();
|
||||
|
||||
// Task: forward broadcast events to WebSocket client (filtered by subscribed topics)
|
||||
let mut send_task = tokio::spawn(async move {
|
||||
while let Ok(msg_json) = broadcast_rx.recv().await {
|
||||
@@ -74,14 +79,15 @@ async fn handle_ws_connection(socket: WebSocket, state: AppState) {
|
||||
};
|
||||
|
||||
if should_send {
|
||||
if ws_sender.send(Message::Text(msg_json.into())).await.is_err() {
|
||||
let mut s = sender_for_broadcast.lock().await;
|
||||
if s.send(Message::Text(msg_json.into())).await.is_err() {
|
||||
break; // Client disconnected
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Task: receive client messages (subscribe/unsubscribe)
|
||||
// Task: receive client messages (subscribe/unsubscribe) and respond to pings
|
||||
let topics_for_recv = subscribed_topics.clone();
|
||||
let mut recv_task = tokio::spawn(async move {
|
||||
while let Some(Ok(msg)) = ws_receiver.next().await {
|
||||
@@ -106,10 +112,14 @@ async fn handle_ws_connection(socket: WebSocket, state: AppState) {
|
||||
}
|
||||
// Silently ignore malformed messages
|
||||
}
|
||||
Message::Close(_) => break,
|
||||
Message::Binary(_) | Message::Ping(_) | Message::Pong(_) => {
|
||||
// Ignore binary frames and WebSocket control frames
|
||||
Message::Ping(data) => {
|
||||
let mut s = sender_for_recv.lock().await;
|
||||
if s.send(Message::Pong(data)).await.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Message::Close(_) => break,
|
||||
Message::Binary(_) | Message::Pong(_) => {}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user