Initial implementation of a signaling server using actix-web.

This commit is contained in:
ghost 2025-08-25 20:28:25 +02:00
commit f61d7b2ca6
5 changed files with 1900 additions and 0 deletions

22
.gitignore vendored Normal file
View File

@ -0,0 +1,22 @@
# Generated by Cargo
# will have compiled files and executables
debug
target
.DS_Store
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# Generated by cargo mutants
# Contains mutation testing data
**/mutants.out*/
# RustRover
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

1785
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

13
Cargo.toml Normal file
View File

@ -0,0 +1,13 @@
[package]
name = "niom-signaling"
version = "0.1.0"
edition = "2024"
[dependencies]
actix-web = "4.11.0"
actix-ws = "0.3.0"
futures-util = "0.3"
tokio = { version = "1.44.1", features = ["full"] }
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.143"
env_logger = "0.11.8"

0
README.md Normal file
View File

80
src/main.rs Normal file
View File

@ -0,0 +1,80 @@
use actix_web::{web, App, Error, HttpRequest, HttpResponse, HttpServer, Result};
use futures_util::StreamExt;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::broadcast;
#[derive(Clone)]
struct AppState {
broadcaster: Arc<broadcast::Sender<SignalingMessage>>
}
#[derive(Debug, Serialize, Deserialize, Clone)]
struct SignalingMessage {
from: String,
to: String,
msg_type: String, // "offer", "answer", "ice-candidate", "text", "screen-share"
data: String // SDP, ICE, Text, Screen-Share metadata
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
env_logger::init();
let (tx, _rx) = broadcast::channel::<SignalingMessage>(1000);
let app_state = AppState {
broadcaster: Arc::new(tx)
};
println!("Server is running on http://localhost:8080");
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(app_state.clone()))
.route("/ws", web::get().to(websocket_handler))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
async fn websocket_handler(
req: HttpRequest,
body: web::Payload,
data: web::Data<AppState>
) -> Result<HttpResponse, Error> {
let (response, mut session, mut msg_stream) = actix_ws::handle(&req, body)?;
let broadcaster = data.broadcaster.clone();
let mut rx = broadcaster.subscribe();
// Handle incoming messages
let broadcaster_clone = broadcaster.clone();
actix_web::rt::spawn(async move {
while let Some(Ok(msg)) = msg_stream.next().await {
match msg {
actix_ws::Message::Text(text) => {
println!("Received text message: {}", text);
if let Ok(signal_msg) = serde_json::from_str::<SignalingMessage>(&text) {
// Broadcast the message to all subscribers (group chat)
// or handle private messages based on `to` field
let _ = broadcaster_clone.send(signal_msg);
}
}
actix_ws::Message::Close(_) => break,
_ => {},
}
}
});
// Handle outgoing messages
actix_web::rt::spawn(async move {
while let Ok(msg) = rx.recv().await {
if let Ok(json) = serde_json::to_string(&msg) {
if session.text(json).await.is_err() {
break;
}
}
}
});
Ok(response)
}