reimplement with fred

This commit is contained in:
2024-05-30 01:55:13 +08:00
parent ddb2f08eed
commit b9d0f0b548
9 changed files with 434 additions and 343 deletions

View File

@ -25,7 +25,7 @@ opentelemetry = { version = "0.21", optional = true }
opentelemetry_sdk = { version = "0.21", optional = true }
opentelemetry-stdout = { version = "0.2", features = ["trace"], optional = true }
redis = { version = "0.25", features = ["tokio-comp"] }
fred = { version = "9", features = ["subscriber-client"] }
sea-orm = { version = "0", features = [ "sqlx-sqlite", "runtime-tokio-rustls", "macros", "mock", "with-chrono", "with-json", "with-uuid" ] }
tera = "1"

View File

@ -1,3 +1,4 @@
pub mod grpc;
mod redisexchange;
pub mod websocket;
pub mod grpc;
pub mod websockets;

View File

@ -1,13 +1,12 @@
use std::{pin::Pin, sync::Arc};
use std::pin::Pin;
use fred::prelude::*;
use futures::stream::*;
use signaling::{signaling_server::*, SignalingMessage};
use tonic::Status;
use tokio::sync::{
mpsc::{Receiver, Sender},
Mutex,
};
use tokio::sync::mpsc::{Receiver, Sender};
/// A wrapper around [`tokio::sync::mpsc::Receiver`] that implements [`Stream`].
///
@ -74,7 +73,7 @@ impl<T> From<Receiver<T>> for ReceiverStream<T> {
}
pub struct SignalingService {
pub redis: redis::Client,
pub redis: fred::types::RedisConfig,
}
impl SignalingService {
@ -105,8 +104,8 @@ impl SignalingService {
async fn handle_with_redis_exchange(
stream: tonic::Streaming<SignalingMessage>,
tx: Sender<Result<SignalingMessage, Status>>,
redis: redis::aio::MultiplexedConnection,
pubsub: Arc<Mutex<redis::aio::PubSub>>,
redis: fred::clients::RedisClient,
pubsub: fred::clients::SubscriberClient,
) {
let (stream_tx, stream_rx) = tokio::sync::mpsc::channel(128);
let (result_tx, result_rx) = tokio::sync::mpsc::channel(128);
@ -135,18 +134,19 @@ impl Signaling for SignalingService {
let in_stream = request.into_inner();
let (tx, rx) = tokio::sync::mpsc::channel(128);
let redis = self.redis.get_multiplexed_tokio_connection().await.unwrap();
let builder = fred::types::Builder::from_config(self.redis.clone());
let redis = builder.build().unwrap();
if let Err(err) = redis.init().await {
return Err(Status::internal(err.to_string()));
}
let pubsub = match self.redis.get_async_pubsub().await {
Ok(pubsub) => pubsub,
Err(err) => return Err(Status::unknown(err.to_string())),
};
let pubsub = builder.build_subscriber_client().unwrap();
if let Err(err) = pubsub.init().await {
return Err(Status::internal(err.to_string()));
}
tokio::spawn(SignalingService::handle_with_redis_exchange(
in_stream,
tx,
redis,
Arc::new(Mutex::new(pubsub)),
in_stream, tx, redis, pubsub,
));
let output_stream = ReceiverStream::new(rx);

View File

@ -1,6 +1,6 @@
use std::sync::Arc;
use redis::AsyncCommands;
use fred::prelude::*;
use signaling::{IceCandidate, SdpMessage, SignalingMessage};
use tokio::sync::{
@ -8,8 +8,6 @@ use tokio::sync::{
Mutex,
};
use futures::StreamExt;
enum RedisChannel {
DiscoverRequest,
DiscoverResponse(String),
@ -58,8 +56,8 @@ impl TryFrom<&str> for RedisChannel {
pub async fn send_message_to_peers(
mut messages_to_peers: Receiver<SignalingMessage>,
messages_from_peers: Sender<SignalingMessage>,
mut redis: redis::aio::MultiplexedConnection,
pubsub: Arc<Mutex<redis::aio::PubSub>>,
redis: fred::clients::RedisClient,
pubsub: fred::clients::SubscriberClient,
) {
let mut name = String::default();
@ -88,8 +86,8 @@ pub async fn send_message_to_peers(
));
}
signaling::signaling_message::Message::DiscoverRequest(()) => {
let peers = match redis
.publish::<_, _, u64>(format!("chatchat:{room}:discover"), name.clone())
let peers: usize = match redis
.publish(format!("chatchat:{room}:discover"), name.clone())
.await
{
Ok(peers) => peers,
@ -98,15 +96,12 @@ pub async fn send_message_to_peers(
break;
}
};
tracing::info!(peers, room, sender = name, "broadcasting discover")
tracing::info!(peers, room, sender = name, "broadcasting discover");
}
signaling::signaling_message::Message::DiscoverResponse(()) => {
let receiver = message.receiver.unwrap();
let peers = match redis
.publish::<_, _, u64>(
format!("chatchat:{room}:discover:{receiver}"),
name.clone(),
)
let peers: usize = match redis
.publish(format!("chatchat:{room}:discover:{receiver}"), name.clone())
.await
{
Ok(peers) => peers,
@ -120,8 +115,8 @@ pub async fn send_message_to_peers(
signaling::signaling_message::Message::SessionOffer(sdp) => {
let msg = serde_json::to_string(&sdp).unwrap();
let receiver = message.receiver.unwrap();
let peers = match redis
.publish::<_, _, u64>(format!("chatchat:{room}:offer:{receiver}"), msg)
let peers: usize = match redis
.publish(format!("chatchat:{room}:offer:{receiver}"), msg)
.await
{
Ok(peers) => peers,
@ -136,8 +131,8 @@ pub async fn send_message_to_peers(
signaling::signaling_message::Message::SessionAnswer(sdp) => {
let msg = serde_json::to_string(&sdp).unwrap();
let receiver = message.receiver.unwrap();
let peers = match redis
.publish::<_, _, u64>(format!("chatchat:{room}:answer:{receiver}"), msg)
let peers: usize = match redis
.publish(format!("chatchat:{room}:answer:{receiver}"), msg)
.await
{
Ok(peers) => peers,
@ -151,8 +146,8 @@ pub async fn send_message_to_peers(
signaling::signaling_message::Message::IceCandidate(candidate) => {
let msg = serde_json::to_string(&candidate).unwrap();
let receiver = message.receiver.unwrap();
let peers = match redis
.publish::<_, _, u64>(format!("chatchat:{room}:icecandidate:{receiver}"), msg)
let peers: usize = match redis
.publish(format!("chatchat:{room}:icecandidate:{receiver}"), msg)
.await
{
Ok(peers) => peers,
@ -166,17 +161,17 @@ pub async fn send_message_to_peers(
}
}
tracing::debug!(name, "stopped send to peer");
let _ = closing_tx.send(()).await;
let _ = closing_tx.send(String::from("websocket exited")).await;
}
pub async fn receive_message_from_peers(
name: String,
room: String,
tx: Sender<SignalingMessage>,
pubsub: Arc<Mutex<redis::aio::PubSub>>,
closing_rx: Arc<Mutex<Receiver<()>>>,
pubsub: fred::clients::SubscriberClient,
closing_rx: Arc<Mutex<Receiver<String>>>,
) {
let mut closing_rx = closing_rx.lock().await;
let mut pubsub = pubsub.lock().await;
// let mut pubsub = pubsub.lock().await;
let discover_request_channel = format!(
"chatchat:{room}:{}",
@ -199,25 +194,19 @@ pub async fn receive_message_from_peers(
RedisChannel::SessionIceCandidate(name.clone()).to_string()
);
tracing::trace!(room, name, channels=?[
&discover_request_channel,
&discover_response_channel,
&session_offer_channel,
&session_answer_channel,
&session_ice_channel,
], "subscribed");
pubsub
.subscribe(&[
discover_request_channel,
discover_response_channel,
session_offer_channel,
session_answer_channel,
session_ice_channel,
])
.await
.unwrap();
let channels = [
&discover_request_channel,
&discover_response_channel,
&session_offer_channel,
&session_answer_channel,
&session_ice_channel,
];
let mut messages = pubsub.on_message();
pubsub.subscribe(&channels).await.unwrap();
tracing::info!(?channels, "subscribed");
// let mut messages = pubsub.on_message();
let mut messages = pubsub.message_rx();
tracing::info!(room, name = name.clone(), "connection ready");
let _ = tx
@ -231,34 +220,41 @@ pub async fn receive_message_from_peers(
loop {
tokio::select! {
_ = closing_rx.recv() => {
message = closing_rx.recv() => {
tracing::warn!(message, "exit due to closing rx");
break;
},
maybe_message = messages.next() => {
let message = if let Some(message) = maybe_message {
message
} else {
break;
maybe_message = messages.recv() => {
let message = match maybe_message {
Ok(message) => message,
Err(error) => {
tracing::warn!(?error, "failed to recv message from redis");
break;
}
};
let channel = match message
.get_channel_name()
.channel.to_string()
.strip_prefix(&format!("chatchat:{}:", room.clone()))
{
Some(channel) => channel,
Some(channel) => channel.to_string(),
_ => continue,
};
let channel = match RedisChannel::try_from(channel) {
let channel = match RedisChannel::try_from(channel.as_str()) {
Ok(channel) => channel,
Err(unrecognized) => {
tracing::warn!(unrecognized, "unrecognized");
continue;
}
};
let payload: String = match message.get_payload() {
Ok(msg) => msg,
_ => continue,
let msg_kind = message.value.kind();
let payload = if let fred::types::RedisValue::String(ref s) = message.value {
s.to_string()
} else {
tracing::warn!(?msg_kind, "message is not string");
continue;
};
match channel {
RedisChannel::DiscoverRequest => {
if payload == name {

View File

@ -1,14 +1,15 @@
use axum::{extract::State, response::IntoResponse};
use fred::prelude::*;
use crate::{signaling::redisexchange, types::AppState};
use axum::extract::ws::Message;
use std::sync::Arc;
use tokio::sync::{Mutex, RwLock};
use tokio::sync::RwLock;
pub async fn handle_message_from_frontend(
mut ws: axum::extract::ws::WebSocket,
redis: redis::aio::MultiplexedConnection,
pubsub: Arc<Mutex<redis::aio::PubSub>>,
redis: fred::clients::RedisClient,
pubsub: fred::clients::SubscriberClient,
) {
let (peer_tx, mut peer_rx) = tokio::sync::mpsc::channel(128);
let (browser_tx, browser_rx) = tokio::sync::mpsc::channel(128);
@ -55,7 +56,6 @@ pub async fn handle_message_from_frontend(
}
}
}
// browser_rx.close();
}
pub async fn handle_ws_upgrade(
@ -63,17 +63,17 @@ pub async fn handle_ws_upgrade(
State(state): State<Arc<RwLock<AppState>>>,
) -> Result<impl IntoResponse, String> {
let state = state.read().await;
let redis: redis::aio::MultiplexedConnection =
match state.redis.get_multiplexed_tokio_connection().await {
Ok(redis) => redis,
Err(err) => return Err(err.to_string()),
};
let pubsub = match state.clone().redis.get_async_pubsub().await {
Ok(pubsub) => pubsub,
Err(err) => return Err(err.to_string()),
};
let builder = fred::types::Builder::from_config(state.redis.clone());
let redis = builder.build().unwrap();
if let Err(err) = redis.init().await {
return Err(err.to_string());
}
Ok(upgrade
.on_upgrade(|ws| handle_message_from_frontend(ws, redis, Arc::new(Mutex::new(pubsub)))))
let pubsub = builder.build_subscriber_client().unwrap();
if let Err(err) = pubsub.init().await {
return Err(err.to_string());
}
Ok(upgrade.on_upgrade(|ws| handle_message_from_frontend(ws, redis, pubsub)))
}

View File

@ -8,5 +8,5 @@ pub struct PageContext {
#[derive(Clone)]
pub struct AppState {
pub templates: tera::Tera,
pub redis: redis::Client,
pub redis: fred::types::RedisConfig,
}

View File

@ -57,13 +57,14 @@ pub struct Config {
impl Default for Config {
fn default() -> Self {
Self {
redis_addr: String::from("redis://127.0.0.1")
redis_addr: String::from("redis://127.0.0.1"),
}
}
}
pub fn make_service(config: Option<Config>) -> Result<IntoMakeService<Router<()>>, Box<dyn std::error::Error>> {
pub fn make_service(
config: Option<Config>,
) -> Result<IntoMakeService<Router<()>>, Box<dyn std::error::Error>> {
let config = config.unwrap_or_default();
let templates = load_templates()?;
@ -72,7 +73,7 @@ pub fn make_service(config: Option<Config>) -> Result<IntoMakeService<Router<()>
#[cfg(feature = "serve-static")]
let router = router.route_service("/static/*path", staticfiles::StaticFiles::strip("/static/"));
let redis = redis::Client::open(config.redis_addr)?;
let redis = fred::types::RedisConfig::from_url(&config.redis_addr)?;
let grpc_service = tonic_web::enable(signaling::signaling_server::SignalingServer::new(
GrpcSignalingService {
@ -87,7 +88,7 @@ pub fn make_service(config: Option<Config>) -> Result<IntoMakeService<Router<()>
"/signaling.Signaling/*rpc",
axum::routing::any_service(grpc_service.clone()),
)
.route("/ws", get(crate::signaling::websocket::handle_ws_upgrade))
.route("/ws", get(crate::signaling::websockets::handle_ws_upgrade))
.with_state(Arc::new(RwLock::new(crate::types::AppState {
templates: templates,
redis,