Skip to content

Commit d58191a

Browse files
committed
add WebSocket support for server events, including /api/wss route and connection handling logic
1 parent 9436bd0 commit d58191a

5 files changed

Lines changed: 120 additions & 6 deletions

File tree

Cargo.lock

Lines changed: 44 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ edition = "2024"
55

66
[dependencies]
77
log = "0.4.29"
8-
axum = { version = "0.8.8", features = ["multipart"] }
8+
axum = { version = "0.8.8", features = ["multipart", "ws"] }
99
tokio = {version = "1.49.0", features = ["full"]}
1010
tower = "0.5.3"
1111
config = "0.15.18"

default.config.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
ism_url = "localhost"
22
ism_port= 5403
3-
log_level = "info"
3+
log_level = "debug"
44
cors_origin = "http://localhost:4200"
55
use_kafka = true
66
push_notification_access_token="oiqhfriuhf"

src/messaging/notifications.rs

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,25 @@ use std::sync::Arc;
22
use std::time::Duration;
33
use axum::{Extension, Json};
44
use axum::extract::{Query, State};
5-
use axum::response::{Sse};
5+
use axum::extract::ws::{Message, WebSocket, WebSocketUpgrade};
6+
use axum::response::{IntoResponse, Sse};
67
use axum::response::sse::Event;
8+
use bytes::Bytes;
79
use chrono::{DateTime, Utc};
810
use futures::Stream;
9-
use log::error;
11+
use tokio::time;
12+
use log::{debug, error};
1013
use serde::Deserialize;
14+
use tokio::sync::broadcast::error::RecvError;
1115
use tokio_stream::wrappers::BroadcastStream;
1216
use tokio_stream::wrappers::errors::BroadcastStreamRecvError;
17+
use tracing::warn;
1318
use uuid::Uuid;
1419
use crate::broadcast::{BroadcastChannel, Notification};
1520
use crate::core::AppState;
1621
use crate::errors::{AppError, AppResponse};
1722
use crate::keycloak::decode::KeycloakToken;
23+
use crate::keycloak::layer::KeycloakAuthLayer;
1824

1925
struct ConnectionGuard {
2026
user_id: Uuid,
@@ -64,6 +70,69 @@ pub async fn stream_server_events(
6470
)
6571
}
6672

73+
74+
pub async fn websocket_server_events(
75+
websocket: WebSocketUpgrade,
76+
Extension(token): Extension<KeycloakToken<String>>
77+
) -> impl IntoResponse {
78+
79+
websocket
80+
.on_failed_upgrade(|error| warn!("Error upgrading websocket: {}", error))
81+
.on_upgrade(move |socket| handle_socket(socket, token.subject.clone()))
82+
}
83+
84+
async fn handle_socket(mut socket: WebSocket, user_id: Uuid) {
85+
86+
let mut broadcast_events = BroadcastChannel::get().subscribe_to_user_events(user_id.clone()).await;
87+
let _guard = ConnectionGuard { user_id };
88+
let mut ping_interval = time::interval(Duration::from_secs(30));
89+
90+
loop {
91+
tokio::select! {
92+
// 1. Handle new broadcasting event:
93+
notification_result = broadcast_events.recv() => {
94+
match notification_result {
95+
Ok(event) => {
96+
let json_msg = serde_json::to_string(&event).unwrap();
97+
let ws_message = Message::text(json_msg);
98+
99+
if socket.send(ws_message).await.is_err() {
100+
error!("Failed to send message to client");
101+
}
102+
}
103+
Err(RecvError::Closed) => {
104+
debug!("Client disconnected or channel closed");
105+
break;
106+
}
107+
Err(RecvError::Lagged(_)) => {
108+
debug!("Client is too slow!")
109+
}
110+
}
111+
}
112+
113+
// 2. Regular ping from ism:
114+
_ = ping_interval.tick() => {
115+
if socket.send(Message::Ping(Bytes::new())).await.is_err() { // connection is dead when we can't send ping
116+
break;
117+
}
118+
}
119+
120+
// 3. Receive messages from the client:
121+
client_msg = socket.recv() => {
122+
match client_msg {
123+
Some(Ok(Message::Close(_))) | None => break, //client is closing connection
124+
Some(Err(_)) => break, //client error
125+
Some(Ok(Message::Pong(_))) => {
126+
debug!("Client has sent Pong");
127+
}
128+
_ => {} //for the future
129+
}
130+
}
131+
}
132+
}
133+
}
134+
135+
67136
#[derive(Deserialize)]
68137
pub struct NotificationQueryParam {
69138
timestamp: DateTime<Utc>

src/messaging/routes.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
use std::sync::Arc;
22
use axum::Router;
3-
use axum::routing::{get, post};
3+
use axum::routing::{any, get, post};
44
use crate::core::AppState;
55
use crate::messaging::handler::handle_send_message;
6-
use crate::messaging::notifications::{get_latest_notification_events, stream_server_events};
6+
use crate::messaging::notifications::{get_latest_notification_events, stream_server_events, websocket_server_events};
77

88
pub fn create_messaging_routes() -> Router<Arc<AppState>> {
99
Router::new() //add new routes here
1010
.route("/api/notifications", get(get_latest_notification_events))
1111
.route("/api/sse", get(stream_server_events))
12+
.route("/api/wss", any(websocket_server_events))
1213
.route("/api/send-msg", post(handle_send_message))
1314
}

0 commit comments

Comments
 (0)