change: 4 issues
- change hub to rwlock for concurrent. - increase size on post noti endpoint. - self-stream for sheet request, handle by write thread. Service may only send 1 chunk. - add get endpoint for getting current online users. Signed-off-by: Pakin <pakin.t@forth.co.th>
This commit is contained in:
parent
4860fd950a
commit
aa008ccd53
7 changed files with 394 additions and 81 deletions
18
src/app.rs
18
src/app.rs
|
|
@ -1,24 +1,20 @@
|
||||||
use crate::websocket::{core::*, helper::read_sheet_config, model::*};
|
use crate::websocket::{core::*, helper::read_sheet_config, model::*};
|
||||||
use axum::{
|
use axum::{
|
||||||
Router,
|
Router,
|
||||||
|
extract::DefaultBodyLimit,
|
||||||
routing::{get, post},
|
routing::{get, post},
|
||||||
serve::ListenerExt,
|
serve::ListenerExt,
|
||||||
};
|
};
|
||||||
use log::{error, info, warn};
|
use log::{error, info};
|
||||||
use redis::TypedCommands;
|
use redis::TypedCommands;
|
||||||
use reqwest::{StatusCode, multipart};
|
use reqwest::{StatusCode, multipart};
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, VecDeque},
|
collections::{HashMap, VecDeque},
|
||||||
env,
|
env,
|
||||||
fs::{self, File},
|
sync::{Arc, RwLock},
|
||||||
io::BufReader,
|
|
||||||
sync::Arc,
|
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
use tokio::{
|
use tokio::sync::{Mutex, mpsc::Sender};
|
||||||
fs::read_dir,
|
|
||||||
sync::{Mutex, mpsc::Sender},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Hub {
|
pub struct Hub {
|
||||||
|
|
@ -106,7 +102,7 @@ pub struct AppState {
|
||||||
pub redis_cli: redis::Client,
|
pub redis_cli: redis::Client,
|
||||||
pub system_tx: tokio::sync::broadcast::Sender<serde_json::Value>,
|
pub system_tx: tokio::sync::broadcast::Sender<serde_json::Value>,
|
||||||
// saved client uid:client uuid
|
// saved client uid:client uuid
|
||||||
pub connectors_mapping: Arc<Mutex<Hub>>,
|
pub connectors_mapping: Arc<RwLock<Hub>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
|
|
@ -126,7 +122,7 @@ impl AppState {
|
||||||
dev_config: dev_config.clone(),
|
dev_config: dev_config.clone(),
|
||||||
redis_cli,
|
redis_cli,
|
||||||
system_tx,
|
system_tx,
|
||||||
connectors_mapping: Arc::new(Mutex::new(Hub {
|
connectors_mapping: Arc::new(RwLock::new(Hub {
|
||||||
clients: HashMap::new(),
|
clients: HashMap::new(),
|
||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
|
|
@ -439,10 +435,12 @@ pub async fn initialize() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
"/syscb",
|
"/syscb",
|
||||||
post(crate::websocket::handler::post_from_other_system),
|
post(crate::websocket::handler::post_from_other_system),
|
||||||
)
|
)
|
||||||
|
.route("/users", get(crate::websocket::handler::get_online_users))
|
||||||
.route("/load-config", post(crate::websocket::handler::post_config))
|
.route("/load-config", post(crate::websocket::handler::post_config))
|
||||||
// .route("/regas", post(request_api_session_key))
|
// .route("/regas", post(request_api_session_key))
|
||||||
.nest("/recipe", rp_router)
|
.nest("/recipe", rp_router)
|
||||||
// .nest("/docs", doc_router)
|
// .nest("/docs", doc_router)
|
||||||
|
.layer(DefaultBodyLimit::max(100 * 1024 * 1024))
|
||||||
.with_state(app_state);
|
.with_state(app_state);
|
||||||
|
|
||||||
// feature: no delay, full throttle
|
// feature: no delay, full throttle
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,9 @@ pub const TIMEOUT: Duration = Duration::from_secs(60 * 5);
|
||||||
/// CONFIG: date format for using in recipe
|
/// CONFIG: date format for using in recipe
|
||||||
pub const LAST_CHANGE_DATE_FORMAT: &str = "%v %T";
|
pub const LAST_CHANGE_DATE_FORMAT: &str = "%v %T";
|
||||||
|
|
||||||
|
/// CONFIG: websocket size limit
|
||||||
|
pub const WEBSOCKET_MAX_BYTES: usize = 2 * 1024 * 1024;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum TxControlMessage {
|
pub enum TxControlMessage {
|
||||||
Payload(serde_json::Value),
|
Payload(serde_json::Value),
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,21 @@
|
||||||
use axum::{
|
use axum::{
|
||||||
Json,
|
Json,
|
||||||
|
body::Bytes,
|
||||||
extract::{Request, State, WebSocketUpgrade, ws::WebSocket},
|
extract::{Request, State, WebSocketUpgrade, ws::WebSocket},
|
||||||
response::IntoResponse,
|
response::IntoResponse,
|
||||||
};
|
};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use log::{info, warn};
|
use log::{error, info, warn};
|
||||||
use redis::TypedCommands;
|
use redis::TypedCommands;
|
||||||
use std::{fs::File, io::BufWriter, sync::Arc};
|
use std::{
|
||||||
use tokio::{sync::Mutex, sync::mpsc, time::Instant};
|
fs::File,
|
||||||
|
io::BufWriter,
|
||||||
|
sync::{Arc, RwLock},
|
||||||
|
};
|
||||||
|
use tokio::{
|
||||||
|
sync::{Mutex, mpsc},
|
||||||
|
time::Instant,
|
||||||
|
};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::{core::*, model::*};
|
use super::{core::*, model::*};
|
||||||
|
|
@ -20,19 +28,97 @@ pub async fn post_from_other_system(
|
||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
Json(msg): Json<SysMessage>,
|
Json(msg): Json<SysMessage>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
let sys_payload = match serde_json::to_value(&msg) {
|
info!("triggering post callback");
|
||||||
Ok(s) => s,
|
let target_receiver = if let Some(to) = msg.payload.get("to") {
|
||||||
Err(_) => {
|
to.as_str().unwrap_or_default().to_string()
|
||||||
|
} else {
|
||||||
|
"".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
let from_service = if let Some(from) = msg.payload.get("from") {
|
||||||
|
from.as_str().unwrap_or_default().to_string()
|
||||||
|
} else {
|
||||||
|
"".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
info!("posting from {from_service} to {target_receiver}");
|
||||||
|
|
||||||
|
match target_receiver.as_str() {
|
||||||
|
"*" => {
|
||||||
|
// send all
|
||||||
|
|
||||||
|
info!("sending all receivers ...");
|
||||||
|
let clients = {
|
||||||
|
let lock = state.connectors_mapping.read().unwrap();
|
||||||
|
lock.clients.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
info!("acquired read lock");
|
||||||
|
let mut send_success_count = 0;
|
||||||
|
let mut send_fail_count = 0;
|
||||||
|
let mut fail_cause = String::new();
|
||||||
|
for (uid, tx) in clients.iter() {
|
||||||
|
if let Err(e) = tx
|
||||||
|
.send(TxControlMessage::Payload(serde_json::json!(msg)))
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
send_fail_count += 1;
|
||||||
|
error!("send to {uid} fail: {e}");
|
||||||
|
fail_cause.push_str(format!("{uid}:{e}\n").as_str());
|
||||||
|
} else {
|
||||||
|
send_success_count += 1;
|
||||||
|
info!("send to {uid} success!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("[send-all] success: {send_success_count}, fail: {send_fail_count}");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
axum::http::StatusCode::OK,
|
||||||
"cannot create payload",
|
serde_json::json!({
|
||||||
|
"success": send_success_count,
|
||||||
|
"fail": send_fail_count,
|
||||||
|
"cause": fail_cause
|
||||||
|
})
|
||||||
|
.to_string(),
|
||||||
|
)
|
||||||
|
.into_response();
|
||||||
|
}
|
||||||
|
recv if !target_receiver.is_empty() => {
|
||||||
|
let recv_sender = {
|
||||||
|
let lock = state.connectors_mapping.read().unwrap();
|
||||||
|
lock.clients.get(recv).cloned()
|
||||||
|
};
|
||||||
|
info!("[send-single] acquired client");
|
||||||
|
|
||||||
|
if let Some(recv_tx) = recv_sender {
|
||||||
|
info!("[send-single] acquired client ok, sending ...");
|
||||||
|
if let Err(e) = recv_tx
|
||||||
|
.send(TxControlMessage::Payload(serde_json::json!(msg)))
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
format!("send fail: {e}"),
|
||||||
|
)
|
||||||
|
.into_response();
|
||||||
|
} else {
|
||||||
|
info!("[send-single] send success");
|
||||||
|
return (axum::http::StatusCode::OK, "send success").into_response();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error!("target user is not connected, user may be offline or disconnected!");
|
||||||
|
return (axum::http::StatusCode::BAD_REQUEST, "user not found").into_response();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
warn!("payload is incorrect from {from_service}, sender was not provided receiver");
|
||||||
|
return (
|
||||||
|
axum::http::StatusCode::BAD_REQUEST,
|
||||||
|
"receiver is empty or wrong type",
|
||||||
)
|
)
|
||||||
.into_response();
|
.into_response();
|
||||||
}
|
}
|
||||||
};
|
|
||||||
match state.system_tx.send(sys_payload) {
|
|
||||||
Ok(_) => (axum::http::StatusCode::OK, "").into_response(),
|
|
||||||
Err(_) => (axum::http::StatusCode::INTERNAL_SERVER_ERROR, "send fail").into_response(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -91,6 +177,22 @@ pub async fn request_api_session_key(
|
||||||
(axum::http::StatusCode::OK, generated).into_response()
|
(axum::http::StatusCode::OK, generated).into_response()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_online_users(State(state): State<Arc<AppState>>) -> impl IntoResponse {
|
||||||
|
let on_connected_clients: Vec<String> = {
|
||||||
|
let lock = state.connectors_mapping.read().unwrap();
|
||||||
|
lock.clients.keys().map(|x| x.to_string()).collect()
|
||||||
|
};
|
||||||
|
|
||||||
|
(
|
||||||
|
axum::http::StatusCode::OK,
|
||||||
|
serde_json::json!({
|
||||||
|
"online": on_connected_clients
|
||||||
|
})
|
||||||
|
.to_string(),
|
||||||
|
)
|
||||||
|
.into_response()
|
||||||
|
}
|
||||||
|
|
||||||
/// Main websocket handler
|
/// Main websocket handler
|
||||||
pub async fn websocket_handler(
|
pub async fn websocket_handler(
|
||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
|
|
@ -115,27 +217,18 @@ pub async fn websocket_handler(
|
||||||
return (axum::http::StatusCode::FORBIDDEN, "".to_string()).into_response();
|
return (axum::http::StatusCode::FORBIDDEN, "".to_string()).into_response();
|
||||||
}
|
}
|
||||||
|
|
||||||
// let mut uid_n = String::new();
|
// TODO: Add more headers?
|
||||||
|
|
||||||
// if let Some(uid) = headers.get("x-auth-uid") {
|
ws.max_frame_size(WEBSOCKET_MAX_BYTES)
|
||||||
// let uid_from_web = uid.to_str().unwrap_or_default().to_string();
|
.max_message_size(WEBSOCKET_MAX_BYTES)
|
||||||
|
.on_failed_upgrade(|error| println!("Error upgrading websocket: {}", error))
|
||||||
// uid_n = uid_from_web;
|
|
||||||
// info!("user connect {uid_n}");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if uid_n.is_empty() {
|
|
||||||
// return (axum::http::StatusCode::BAD_REQUEST, "").into_response();
|
|
||||||
// }
|
|
||||||
|
|
||||||
ws.on_failed_upgrade(|error| println!("Error upgrading websocket: {}", error))
|
|
||||||
.on_upgrade(async |s| handle_socket(s, state_clone, hub_clone).await.unwrap_or(()))
|
.on_upgrade(async |s| handle_socket(s, state_clone, hub_clone).await.unwrap_or(()))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_socket(
|
async fn handle_socket(
|
||||||
socket: WebSocket,
|
socket: WebSocket,
|
||||||
state: Arc<AppState>,
|
state: Arc<AppState>,
|
||||||
hub: Arc<Mutex<Hub>>,
|
hub: Arc<RwLock<Hub>>,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let (sender, receiver) = socket.split();
|
let (sender, receiver) = socket.split();
|
||||||
// internal channel
|
// internal channel
|
||||||
|
|
@ -163,27 +256,32 @@ async fn handle_socket(
|
||||||
info!("{} connected", temp_session);
|
info!("{} connected", temp_session);
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut h = hub.lock().await;
|
let mut h = hub.write().unwrap();
|
||||||
h.clients.insert(temp_session.clone(), tx.clone());
|
h.clients.insert(temp_session.clone(), tx.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
let user_sys_rx = state.system_tx.subscribe();
|
// NOTE: disable from cause system tx could directly send to client rx
|
||||||
|
// without sending to system rx.
|
||||||
|
// let user_sys_rx = state.system_tx.subscribe();
|
||||||
|
|
||||||
let last_seen = Arc::new(Mutex::new(Instant::now()));
|
let last_seen = Arc::new(Mutex::new(Instant::now()));
|
||||||
|
|
||||||
let reader_last_seen = last_seen.clone();
|
let reader_last_seen = last_seen.clone();
|
||||||
let watchdog_last_seen = last_seen.clone();
|
let watchdog_last_seen = last_seen.clone();
|
||||||
|
|
||||||
let sender = tokio::spawn(super::rw::write(sender, rx, user.clone(), hub.clone()));
|
let hub_for_write = hub.clone();
|
||||||
|
let hub_for_read = hub.clone();
|
||||||
|
|
||||||
|
let sender = tokio::spawn(super::rw::write(sender, rx, user.clone(), hub_for_write));
|
||||||
let reader = tokio::spawn(super::rw::read(
|
let reader = tokio::spawn(super::rw::read(
|
||||||
state,
|
state,
|
||||||
receiver,
|
receiver,
|
||||||
tx.clone(),
|
tx.clone(),
|
||||||
reader_last_seen,
|
reader_last_seen,
|
||||||
user.clone(),
|
user.clone(),
|
||||||
hub.clone(),
|
hub_for_read,
|
||||||
));
|
));
|
||||||
let callback_to_client = super::rw::recv_sys_msg_send_back_client(tx.clone(), user_sys_rx);
|
// let callback_to_client = super::rw::recv_sys_msg_send_back_client(tx.clone(), user_sys_rx);
|
||||||
|
|
||||||
let watchdog = super::tasks::watchdog::get_watchdog_task(
|
let watchdog = super::tasks::watchdog::get_watchdog_task(
|
||||||
tx,
|
tx,
|
||||||
|
|
@ -192,27 +290,40 @@ async fn handle_socket(
|
||||||
hub.clone(),
|
hub.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let (rf, sf, cbc, wds) = tokio::join!(reader, sender, callback_to_client, watchdog);
|
let (rf, sf, wds) = tokio::join!(reader, sender, watchdog);
|
||||||
|
|
||||||
if let Ok(rf_js) = rf
|
if let Ok(rf_js) = rf
|
||||||
&& let Ok(sf_js) = sf
|
&& let Ok(sf_js) = sf
|
||||||
{
|
{
|
||||||
|
let user = user.clone().lock().await.to_string();
|
||||||
info!(
|
info!(
|
||||||
"read end ok: {}, write end ok: {} [{}]",
|
"read end ok: {}, write end ok: {} [{}]",
|
||||||
rf_js.is_ok(),
|
rf_js.is_ok(),
|
||||||
sf_js.is_ok(),
|
sf_js.is_ok(),
|
||||||
user.clone().lock().await.to_string()
|
user.clone()
|
||||||
);
|
);
|
||||||
if !cbc.is_finished() {
|
|
||||||
info!("sys rx still running");
|
|
||||||
cbc.abort();
|
|
||||||
|
|
||||||
if cbc.await.unwrap_err().is_cancelled() {
|
|
||||||
info!("sys rx force stop ...");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !wds.is_finished() {
|
if !wds.is_finished() {
|
||||||
info!("watchdog still existed");
|
info!("watchdog still existed");
|
||||||
|
|
||||||
|
wds.abort();
|
||||||
|
|
||||||
|
if wds.await.unwrap_err().is_cancelled() {
|
||||||
|
info!("watchdog force stop");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut lock = hub.write().unwrap();
|
||||||
|
|
||||||
|
if lock.clients.contains_key(&user) {
|
||||||
|
warn!("user still existed! {user}, removing key ...");
|
||||||
|
lock.clients.remove(&user);
|
||||||
|
// check again
|
||||||
|
warn!(
|
||||||
|
"after remove user, exist: {}",
|
||||||
|
lock.clients.contains_key(&user)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ use crate::{
|
||||||
websocket::{plugins::call_plugin_if_existed, tasks},
|
websocket::{plugins::call_plugin_if_existed, tasks},
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
sync::{Arc, atomic::AtomicBool},
|
collections::HashMap,
|
||||||
|
sync::{Arc, RwLock},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -32,7 +33,7 @@ pub async fn read(
|
||||||
tx: Sender<TxControlMessage>,
|
tx: Sender<TxControlMessage>,
|
||||||
last_seen: Arc<Mutex<Instant>>, // cmd_atom: crossbeam_queue::ArrayQueue<CommandRequestPayload>,
|
last_seen: Arc<Mutex<Instant>>, // cmd_atom: crossbeam_queue::ArrayQueue<CommandRequestPayload>,
|
||||||
uid: Arc<Mutex<String>>,
|
uid: Arc<Mutex<String>>,
|
||||||
hub: Arc<Mutex<Hub>>,
|
hub: Arc<RwLock<Hub>>,
|
||||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let redis = state.redis_cli.clone();
|
let redis = state.redis_cli.clone();
|
||||||
let config = state.dev_config.clone();
|
let config = state.dev_config.clone();
|
||||||
|
|
@ -127,9 +128,15 @@ pub async fn read(
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
"sheet" if req.payload.is_some() => {
|
"sheet" if req.payload.is_some() => {
|
||||||
if tasks::sheet::handle_sheet_request(config.clone(), redis.clone(), req)
|
if tasks::sheet::handle_sheet_request(
|
||||||
.await
|
config.clone(),
|
||||||
.is_err()
|
redis.clone(),
|
||||||
|
tx.clone(),
|
||||||
|
req,
|
||||||
|
uid_clone.clone(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.is_err()
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -199,7 +206,7 @@ pub async fn read(
|
||||||
|
|
||||||
// remove current uid
|
// remove current uid
|
||||||
{
|
{
|
||||||
let mut h = hub.try_lock().unwrap();
|
let mut h = hub.write().unwrap();
|
||||||
let curr_user = uid.try_lock().unwrap().to_string();
|
let curr_user = uid.try_lock().unwrap().to_string();
|
||||||
|
|
||||||
if let Some(ent) = h.clients.remove_entry(&curr_user) {
|
if let Some(ent) = h.clients.remove_entry(&curr_user) {
|
||||||
|
|
@ -228,7 +235,10 @@ pub async fn read(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("[read] canceling sys rx ...");
|
info!(
|
||||||
|
"[read][{}] canceling sys rx ...",
|
||||||
|
uid_clone.lock().await.to_string()
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -237,8 +247,11 @@ pub async fn write(
|
||||||
mut sender: SplitSink<WebSocket, Message>,
|
mut sender: SplitSink<WebSocket, Message>,
|
||||||
mut rx: Receiver<TxControlMessage>,
|
mut rx: Receiver<TxControlMessage>,
|
||||||
uid: Arc<Mutex<String>>,
|
uid: Arc<Mutex<String>>,
|
||||||
hub: Arc<Mutex<Hub>>,
|
hub: Arc<RwLock<Hub>>,
|
||||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
// only allow each stream type for 1 request
|
||||||
|
let pending_stream_requests = Arc::new(RwLock::new(HashMap::new()));
|
||||||
|
|
||||||
while let Some(res) = rx.recv().await {
|
while let Some(res) = rx.recv().await {
|
||||||
match res {
|
match res {
|
||||||
TxControlMessage::Payload(res) => {
|
TxControlMessage::Payload(res) => {
|
||||||
|
|
@ -266,10 +279,50 @@ pub async fn write(
|
||||||
&& let Some(recv_ident) = res_payload_val.get("to")
|
&& let Some(recv_ident) = res_payload_val.get("to")
|
||||||
&& let Some(recv_ident_str) = recv_ident.as_str()
|
&& let Some(recv_ident_str) = recv_ident.as_str()
|
||||||
&& (current_uid.to_string().eq(recv_ident_str)
|
&& (current_uid.to_string().eq(recv_ident_str)
|
||||||
|| current_uid.to_string().eq("*"))
|
|| recv_ident_str.to_string().eq("*"))
|
||||||
{
|
{
|
||||||
let payload_size = res.to_string().len();
|
let payload_size = res.to_string().len();
|
||||||
|
|
||||||
|
if tasks::sheet::is_tx_stream_type(res.clone()) {
|
||||||
|
let rid = res_payload_val
|
||||||
|
.get("request_id")
|
||||||
|
.unwrap_or_default()
|
||||||
|
.as_str();
|
||||||
|
let rtype = res_n
|
||||||
|
.get("type")
|
||||||
|
.unwrap_or_default()
|
||||||
|
.as_str()
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let mut pending = {
|
||||||
|
let lock = pending_stream_requests.write().unwrap();
|
||||||
|
lock
|
||||||
|
};
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"register stream type: {rtype}, with request id: {} and allow add to pending: {}",
|
||||||
|
rid.unwrap().to_string(),
|
||||||
|
!pending.contains_key(rtype)
|
||||||
|
);
|
||||||
|
|
||||||
|
if !rtype.is_empty() && !pending.contains_key(rtype) {
|
||||||
|
// allow set pending
|
||||||
|
pending.insert(rtype.to_string(), rid.unwrap().to_string());
|
||||||
|
info!(
|
||||||
|
"add pending stream: {} ---> {}",
|
||||||
|
rtype,
|
||||||
|
rid.unwrap().to_string()
|
||||||
|
);
|
||||||
|
} else if rtype.is_empty() {
|
||||||
|
warn!("request stream type is empty!");
|
||||||
|
} else {
|
||||||
|
// blocking
|
||||||
|
warn!("request more than once, please wait until current finish!");
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if payload_size >= 100000 {
|
if payload_size >= 100000 {
|
||||||
// large payload
|
// large payload
|
||||||
warn!(
|
warn!(
|
||||||
|
|
@ -278,16 +331,101 @@ pub async fn write(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = sender.send(res.to_string().into()).await;
|
// handle check if response has been set as pending before
|
||||||
|
|
||||||
|
let stream_ref = res_payload_val.get("ref").unwrap_or_default();
|
||||||
|
|
||||||
|
let pending_has_key = {
|
||||||
|
let lock = pending_stream_requests.read().unwrap();
|
||||||
|
lock.contains_key(
|
||||||
|
format!("stream-{}", stream_ref.as_str().unwrap_or_default()).as_str(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let stream_chunk_id = {
|
||||||
|
let lock = pending_stream_requests.read().unwrap();
|
||||||
|
lock.get(
|
||||||
|
format!("stream-{}", stream_ref.as_str().unwrap_or_default()).as_str(),
|
||||||
|
)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_default()
|
||||||
|
};
|
||||||
|
|
||||||
|
if pending_has_key {
|
||||||
|
// has set, do iterate now
|
||||||
|
|
||||||
|
// gen payload size
|
||||||
|
let size_per_payload = 10000;
|
||||||
|
|
||||||
|
let chars: Vec<char> = res.to_string().chars().collect();
|
||||||
|
let split = &chars
|
||||||
|
.chunks(size_per_payload)
|
||||||
|
.map(|chunk| chunk.iter().collect::<String>())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let header = serde_json::json!({
|
||||||
|
"type": format!("raw_stream_{}", stream_ref.as_str().unwrap_or_default()),
|
||||||
|
"payload": {
|
||||||
|
"to": recv_ident_str,
|
||||||
|
"total_chunks": split.len(),
|
||||||
|
"size_per_chunk": size_per_payload,
|
||||||
|
"request_id": stream_chunk_id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let footer = serde_json::json!({
|
||||||
|
"type": format!("raw_stream_end_{}", stream_ref.as_str().unwrap_or_default()),
|
||||||
|
"payload": {
|
||||||
|
"to": recv_ident_str,
|
||||||
|
"request_id": stream_chunk_id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let _ = sender.send(header.to_string().into()).await?;
|
||||||
|
|
||||||
|
for (idx, raw_payload) in split.iter().enumerate() {
|
||||||
|
let raw_chunk_payload = serde_json::json!({
|
||||||
|
"type": format!("raw_stream_chunk_{}", stream_ref.as_str().unwrap_or_default()),
|
||||||
|
"payload": {
|
||||||
|
"to": recv_ident_str,
|
||||||
|
"raw": raw_payload.clone(),
|
||||||
|
"idx": idx,
|
||||||
|
"request_id": stream_chunk_id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let _ = sender.feed(raw_chunk_payload.to_string().into()).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(e) = sender.flush().await {
|
||||||
|
error!("flushing stream failed: {e}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// end stream
|
||||||
|
let mut lock = pending_stream_requests.write().unwrap();
|
||||||
|
|
||||||
|
lock.remove(
|
||||||
|
format!("stream-{}", stream_ref.as_str().unwrap_or_default())
|
||||||
|
.as_str(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = sender.send(footer.to_string().into()).await;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
if let Err(e) = sender.send(res.to_string().into()).await {
|
||||||
|
error!("[write] send payload fail; len={payload_size}, reason: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// show error by case
|
// show error by case
|
||||||
let clients: Vec<String> = hub
|
let clients: Vec<String> = {
|
||||||
.lock()
|
let lock = hub.read().unwrap();
|
||||||
.await
|
|
||||||
.clients
|
lock.clients.keys().map(|x| x.to_string()).collect()
|
||||||
.keys()
|
};
|
||||||
.map(|x| x.to_string())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// step errors
|
// step errors
|
||||||
if let Some(res_n) = res.as_object()
|
if let Some(res_n) = res.as_object()
|
||||||
|
|
@ -301,7 +439,7 @@ pub async fn write(
|
||||||
if clients.contains(&recv_ident_str.to_string())
|
if clients.contains(&recv_ident_str.to_string())
|
||||||
&& current_uid.ne(&recv_ident_str.to_string())
|
&& current_uid.ne(&recv_ident_str.to_string())
|
||||||
{
|
{
|
||||||
warn!("oops! receiving other receiver's messages. Ignore this");
|
// warn!("oops! receiving other receiver's messages. Ignore this");
|
||||||
} else {
|
} else {
|
||||||
error!("receiver not existed or already went offline");
|
error!("receiver not existed or already went offline");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
use crate::app::*;
|
use crate::app::*;
|
||||||
use crate::websocket::{core::*, model::*};
|
use crate::websocket::{core::*, model::*};
|
||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
use std::sync::Arc;
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
use tokio::sync::{Mutex, mpsc::Sender};
|
use tokio::sync::{Mutex, mpsc::Sender};
|
||||||
|
|
||||||
/// Handle request of command type from websocket (read)
|
/// Handle request of command type from websocket (read)
|
||||||
|
|
@ -9,7 +10,7 @@ pub async fn handle_auth_request(
|
||||||
state: Arc<AppState>,
|
state: Arc<AppState>,
|
||||||
tx: Sender<TxControlMessage>,
|
tx: Sender<TxControlMessage>,
|
||||||
req: WebsocketMessageRequest,
|
req: WebsocketMessageRequest,
|
||||||
hub: Arc<Mutex<Hub>>,
|
hub: Arc<RwLock<Hub>>,
|
||||||
curr_uid: Arc<Mutex<String>>,
|
curr_uid: Arc<Mutex<String>>,
|
||||||
) -> WebsocketMessageResult {
|
) -> WebsocketMessageResult {
|
||||||
// do command send to other services
|
// do command send to other services
|
||||||
|
|
@ -28,7 +29,10 @@ pub async fn handle_auth_request(
|
||||||
|
|
||||||
if !new_uid.is_empty() {
|
if !new_uid.is_empty() {
|
||||||
let old_uid = curr_uid.try_lock().unwrap().to_string();
|
let old_uid = curr_uid.try_lock().unwrap().to_string();
|
||||||
let mut h = hub.try_lock().unwrap();
|
let mut h = {
|
||||||
|
let lock = hub.write().unwrap();
|
||||||
|
lock
|
||||||
|
};
|
||||||
if let Some(ent) = h.clients.remove_entry(&old_uid) {
|
if let Some(ent) = h.clients.remove_entry(&old_uid) {
|
||||||
let curr_connection = ent.1;
|
let curr_connection = ent.1;
|
||||||
|
|
||||||
|
|
@ -36,7 +40,7 @@ pub async fn handle_auth_request(
|
||||||
|
|
||||||
// case auth success but already have some tx left
|
// case auth success but already have some tx left
|
||||||
if let Some(old_tx) = h.clients.insert(new_uid.clone(), curr_connection) {
|
if let Some(old_tx) = h.clients.insert(new_uid.clone(), curr_connection) {
|
||||||
warn!("disconnecting old connection");
|
warn!("[auth][{}] disconnecting old connection", old_uid.clone());
|
||||||
let _ = old_tx.send(TxControlMessage::CloseExist);
|
let _ = old_tx.send(TxControlMessage::CloseExist);
|
||||||
}
|
}
|
||||||
info!("update re-new auth successful ---> {}", new_uid.clone());
|
info!("update re-new auth successful ---> {}", new_uid.clone());
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,20 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::DevConfig,
|
app::DevConfig,
|
||||||
websocket::{core::*, model::*},
|
websocket::{core::*, model::*},
|
||||||
};
|
};
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
use redis::TypedCommands;
|
use redis::TypedCommands;
|
||||||
|
use tokio::sync::{Mutex, mpsc::Sender};
|
||||||
|
|
||||||
/// Handle request of sheet type from websocket (read)
|
/// Handle request of sheet type from websocket (read)
|
||||||
pub async fn handle_sheet_request(
|
pub async fn handle_sheet_request(
|
||||||
config: DevConfig,
|
config: DevConfig,
|
||||||
redis: redis::Client,
|
redis: redis::Client,
|
||||||
|
tx: Sender<TxControlMessage>,
|
||||||
req: WebsocketMessageRequest,
|
req: WebsocketMessageRequest,
|
||||||
|
uid_clone: Arc<Mutex<String>>,
|
||||||
) -> WebsocketMessageResult {
|
) -> WebsocketMessageResult {
|
||||||
// CommandRequestPayload struct-like
|
// CommandRequestPayload struct-like
|
||||||
|
|
||||||
|
|
@ -26,17 +31,28 @@ pub async fn handle_sheet_request(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
info!(
|
// info!(
|
||||||
"get sheet request: {}, {:?}",
|
// "get sheet request: {}, {:?}",
|
||||||
payload_sheet_request.srv_name, payload_sheet_request
|
// payload_sheet_request.srv_name, payload_sheet_request
|
||||||
);
|
// );
|
||||||
|
|
||||||
let parameters = payload_sheet_request
|
let parameters = payload_sheet_request
|
||||||
.values
|
.values
|
||||||
.get("param")
|
.get("param")
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
// TODO: will be changed to config from yaml file
|
let stream_mode = payload_sheet_request
|
||||||
|
.values
|
||||||
|
.get("stream")
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let request_id = payload_sheet_request
|
||||||
|
.values
|
||||||
|
.get("request_id")
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let uidd = uid_clone.clone().lock().await.to_string();
|
||||||
|
|
||||||
let ch_target = if let Some(pm) = parameters.as_str()
|
let ch_target = if let Some(pm) = parameters.as_str()
|
||||||
&& config.check_sheet_endpoints(pm)
|
&& config.check_sheet_endpoints(pm)
|
||||||
{
|
{
|
||||||
|
|
@ -58,5 +74,39 @@ pub async fn handle_sheet_request(
|
||||||
error!("error on publish result cmd: {e:?}");
|
error!("error on publish result cmd: {e:?}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(stream_flag) = stream_mode.as_bool()
|
||||||
|
&& let Some(request_id) = request_id.as_str()
|
||||||
|
&& stream_flag
|
||||||
|
&& !request_id.is_empty()
|
||||||
|
{
|
||||||
|
let _ = tx
|
||||||
|
.send(TxControlMessage::Payload(serde_json::json!({
|
||||||
|
"type": format!("stream-{ch_target}"),
|
||||||
|
"payload": {
|
||||||
|
"request_id": request_id.to_string(),
|
||||||
|
"to": uidd
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_tx_stream_type(raw: serde_json::Value) -> bool {
|
||||||
|
// expect request id
|
||||||
|
// type must start with stream
|
||||||
|
let tx_type = raw.get("type").unwrap_or_default();
|
||||||
|
let payload = raw.get("payload").unwrap_or_default();
|
||||||
|
|
||||||
|
if let Some(tx_t) = tx_type.as_str()
|
||||||
|
&& let Some(request_id) = payload.get("request_id")
|
||||||
|
&& let Some(rid) = request_id.as_str()
|
||||||
|
&& tx_t.starts_with("stream-")
|
||||||
|
&& !rid.is_empty()
|
||||||
|
{
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
use crate::{app::Hub, websocket::core::*};
|
use crate::{app::Hub, websocket::core::*};
|
||||||
use log::{info, warn};
|
use log::{info, warn};
|
||||||
use std::{ops::Sub, sync::Arc, time::Duration};
|
use std::{
|
||||||
|
ops::Sub,
|
||||||
|
sync::{Arc, RwLock},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
sync::{Mutex, mpsc::Sender},
|
sync::{Mutex, mpsc::Sender},
|
||||||
task::JoinHandle,
|
task::JoinHandle,
|
||||||
|
|
@ -11,16 +15,21 @@ pub async fn get_watchdog_task(
|
||||||
tx: Sender<TxControlMessage>,
|
tx: Sender<TxControlMessage>,
|
||||||
watchdog_last_seen: Arc<Mutex<Instant>>,
|
watchdog_last_seen: Arc<Mutex<Instant>>,
|
||||||
user: Arc<Mutex<String>>,
|
user: Arc<Mutex<String>>,
|
||||||
hub: Arc<Mutex<Hub>>,
|
hub: Arc<RwLock<Hub>>,
|
||||||
) -> JoinHandle<()> {
|
) -> JoinHandle<()> {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let uc = user.clone().lock().await.to_string();
|
let uc = user.clone().lock().await.to_string();
|
||||||
info!("start watchdog for {uc}");
|
info!("start watchdog for {uc}");
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
tokio::time::sleep(Duration::from_secs(2)).await;
|
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||||
|
|
||||||
|
let h = {
|
||||||
|
let lock = hub.read().unwrap();
|
||||||
|
lock.clone()
|
||||||
|
};
|
||||||
|
|
||||||
{
|
{
|
||||||
let h = hub.lock().await;
|
|
||||||
let curr_user = user.lock().await.to_string();
|
let curr_user = user.lock().await.to_string();
|
||||||
|
|
||||||
// info!("{}: checking invalid ...", curr_user);
|
// info!("{}: checking invalid ...", curr_user);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue