Web Socket & http Server on same port
This commit is contained in:
parent
4170b9f387
commit
78a0ebd18e
7 changed files with 250 additions and 69 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
|
@ -21,7 +21,9 @@ dependencies = [
|
|||
"hex",
|
||||
"hkdf",
|
||||
"hmac",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"json",
|
||||
"native-tls",
|
||||
"once_cell",
|
||||
|
|
@ -39,6 +41,7 @@ dependencies = [
|
|||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
"tokio-util",
|
||||
"tower",
|
||||
"tungstenite",
|
||||
"uuid",
|
||||
"walkdir",
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ hex = "*"
|
|||
hkdf = "*"
|
||||
hmac = "*"
|
||||
hyper = { version = "*", features = ["full"] }
|
||||
hyper-util = "*"
|
||||
http-body-util = "*"
|
||||
json = "*"
|
||||
native-tls = { version = "*", default-features = false }
|
||||
once_cell = "1.21.3"
|
||||
|
|
@ -37,6 +39,7 @@ sysinfo = "0.30"
|
|||
tokio = { version = "*", features = ["full"] }
|
||||
tokio-util = { version = "*", features = ["full"] }
|
||||
tokio-tungstenite = { version = "*", features = ["native-tls"] }
|
||||
tower = "*"
|
||||
tungstenite = "*"
|
||||
uuid = { version = "*", features = ["v4"] }
|
||||
walkdir = "2.5.0"
|
||||
|
|
|
|||
|
|
@ -6,23 +6,28 @@ use crate::data::communication::{CommunicationType, CommunicationValue, DataType
|
|||
use aes_gcm::{Aes256Gcm, KeyInit, Nonce, aead::Aead};
|
||||
use async_tungstenite::WebSocketReceiver;
|
||||
use async_tungstenite::WebSocketSender;
|
||||
use async_tungstenite::tungstenite::Message;
|
||||
use async_tungstenite::tungstenite::Utf8Bytes;
|
||||
use async_tungstenite::{WebSocketStream, tungstenite::Message};
|
||||
use base64::{Engine as _, engine::general_purpose::STANDARD};
|
||||
use futures::SinkExt;
|
||||
use futures::stream::SplitSink;
|
||||
use futures::stream::SplitStream;
|
||||
use hkdf::Hkdf;
|
||||
use hyper::upgrade::Upgraded;
|
||||
use hyper_util::rt::TokioIo;
|
||||
use json::JsonValue;
|
||||
use rand::{Rng, distributions::Alphanumeric};
|
||||
use sha2::Sha256;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::sync::RwLock;
|
||||
use tokio_tungstenite::WebSocketStream;
|
||||
use tokio_util::compat::Compat;
|
||||
use uuid::Uuid;
|
||||
use x448::PublicKey;
|
||||
pub struct CommunityConnection {
|
||||
pub sender: Arc<RwLock<WebSocketSender<Compat<tokio::net::TcpStream>>>>,
|
||||
pub receiver: Arc<RwLock<WebSocketReceiver<Compat<tokio::net::TcpStream>>>>,
|
||||
pub sender: Arc<RwLock<SplitSink<WebSocketStream<TokioIo<Upgraded>>, Message>>>,
|
||||
pub receiver: Arc<RwLock<SplitStream<WebSocketStream<TokioIo<Upgraded>>>>>,
|
||||
pub user_id: Arc<RwLock<Option<Uuid>>>,
|
||||
pub community: Arc<RwLock<Option<Arc<Community>>>>,
|
||||
identified: Arc<RwLock<bool>>,
|
||||
|
|
@ -33,8 +38,8 @@ pub struct CommunityConnection {
|
|||
}
|
||||
impl CommunityConnection {
|
||||
pub fn new(
|
||||
sender: WebSocketSender<Compat<tokio::net::TcpStream>>,
|
||||
receiver: WebSocketReceiver<Compat<tokio::net::TcpStream>>,
|
||||
sender: SplitSink<WebSocketStream<TokioIo<Upgraded>>, Message>,
|
||||
receiver: SplitStream<WebSocketStream<TokioIo<Upgraded>>>,
|
||||
community: Arc<Community>,
|
||||
) -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
|
|
@ -50,13 +55,9 @@ impl CommunityConnection {
|
|||
})
|
||||
}
|
||||
pub async fn send_message(&self, message: &CommunicationValue) {
|
||||
let mut session = self.sender.write().await;
|
||||
session
|
||||
.send(Message::Text(Utf8Bytes::from(
|
||||
message.to_json().to_string(),
|
||||
)))
|
||||
.await
|
||||
.unwrap();
|
||||
let mut sender = self.sender.write().await; // Access the SplitSink
|
||||
let message_text = Message::Text(Utf8Bytes::from(message.to_json().to_string()));
|
||||
sender.send(message_text).await.unwrap(); // Send the message via the SplitSink
|
||||
}
|
||||
pub async fn get_community(&self) -> Option<Arc<Community>> {
|
||||
self.community.read().await.clone()
|
||||
|
|
@ -398,8 +399,8 @@ impl CommunityConnection {
|
|||
self.send_message(&error).await;
|
||||
}
|
||||
pub async fn close(&self) {
|
||||
let mut session = self.sender.write().await;
|
||||
let _ = session.close(None).await;
|
||||
let mut sender = self.sender.write().await;
|
||||
let _ = sender.close().await;
|
||||
}
|
||||
pub async fn handle_close(self: Arc<Self>) {
|
||||
if self.is_identified().await {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ mod server;
|
|||
mod users;
|
||||
mod util;
|
||||
|
||||
use crate::communities::community::Community;
|
||||
use crate::communities::community_manager;
|
||||
use crate::communities::interactables::registry;
|
||||
use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes};
|
||||
|
|
@ -25,7 +26,7 @@ use crate::gui::{log_panel, ratatui_interface};
|
|||
use crate::langu::language_creator;
|
||||
use crate::langu::language_manager::format;
|
||||
use crate::omikron::omikron_connection::OmikronConnection;
|
||||
use crate::server::socket::start;
|
||||
use crate::server::server::start;
|
||||
use crate::users::user_manager;
|
||||
use crate::util::config_util::CONFIG;
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
pub mod server;
|
||||
pub mod socket;
|
||||
|
|
|
|||
160
src/server/server.rs
Normal file
160
src/server/server.rs
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
use color_eyre::eyre::Ok;
|
||||
use futures::{StreamExt, TryFutureExt};
|
||||
use http_body_util::Full;
|
||||
use hyper::body::Bytes;
|
||||
use hyper::{
|
||||
Request as HttpRequest, Response as HttpResponse, StatusCode, body::Incoming,
|
||||
server::conn::http1, upgrade,
|
||||
};
|
||||
use std::error::Error;
|
||||
use std::io::{self};
|
||||
use std::{future::Future, pin::Pin, time::Duration};
|
||||
use tokio::net::TcpListener;
|
||||
use tower::Service;
|
||||
use warp::filters::log::log;
|
||||
|
||||
use crate::gui::log_panel::log_message;
|
||||
use crate::langu::language_manager::format;
|
||||
use crate::server::socket::handle;
|
||||
use hyper_util::rt::tokio::TokioIo;
|
||||
use hyper_util::service::TowerToHyperService;
|
||||
use tokio_tungstenite::WebSocketStream;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct HttpService;
|
||||
|
||||
impl Service<HttpRequest<Incoming>> for HttpService {
|
||||
type Response = HttpResponse<Full<Bytes>>;
|
||||
type Error = io::Error;
|
||||
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
|
||||
|
||||
fn poll_ready(
|
||||
&mut self,
|
||||
_cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Result<(), Self::Error>> {
|
||||
std::task::Poll::Ready(std::io::Result::Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, req: HttpRequest<Incoming>) -> Self::Future {
|
||||
let path = req.uri().path().to_string();
|
||||
let headers = req.headers().clone();
|
||||
let upgrades = upgrade::on(req);
|
||||
|
||||
let fut = async move {
|
||||
if path.starts_with("/ws")
|
||||
&& headers
|
||||
.get("connection")
|
||||
.map(|v| v.to_str().unwrap_or("").contains("Upgrade"))
|
||||
.unwrap_or(false)
|
||||
&& headers.get("upgrade").map(|v| v.to_str().unwrap_or("")) == Some("websocket")
|
||||
{
|
||||
log_message("Attempting WebSocket upgrade on /ws");
|
||||
|
||||
let response = HttpResponse::builder()
|
||||
.status(StatusCode::SWITCHING_PROTOCOLS)
|
||||
.header("Upgrade", "websocket")
|
||||
.header("Connection", "Upgrade")
|
||||
.body(Full::new(Bytes::from("")))
|
||||
.unwrap();
|
||||
tokio::spawn(async move {
|
||||
match upgrades.await {
|
||||
std::result::Result::Ok(upgraded_stream) => {
|
||||
let raw_stream = TokioIo::new(upgraded_stream);
|
||||
|
||||
let handshake_result = WebSocketStream::from_raw_socket(
|
||||
raw_stream,
|
||||
tungstenite::protocol::Role::Server,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
let (writer, reader) = handshake_result.split();
|
||||
handle(path, writer, reader);
|
||||
}
|
||||
Err(e) => {
|
||||
log_message(format!(
|
||||
"WebSocket upgrade failed after response: {:?}",
|
||||
e
|
||||
));
|
||||
}
|
||||
}
|
||||
});
|
||||
Ok(response)
|
||||
} else {
|
||||
let (status, body_text) = match path.as_str() {
|
||||
"/" => (
|
||||
StatusCode::OK,
|
||||
"Barebones Server: Try connecting to WebSocket at ws://<host>:<port>/ws or check /status.",
|
||||
),
|
||||
"/status" => (StatusCode::OK, "HTTP Server Status: Barebones Online"),
|
||||
_ => (StatusCode::NOT_FOUND, "404 Not Found"),
|
||||
};
|
||||
let body = Full::new(Bytes::from(body_text.to_string()));
|
||||
let response = HttpResponse::builder()
|
||||
.status(status)
|
||||
.header(hyper::header::CONTENT_TYPE, "text/plain")
|
||||
.body(body)
|
||||
.unwrap();
|
||||
Ok(response)
|
||||
}
|
||||
};
|
||||
|
||||
Box::pin(fut.map_err(|err| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("Error in request handling: {}", err),
|
||||
)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn start(port: u16) -> bool {
|
||||
// Bind to the port
|
||||
let listener = TcpListener::bind(format!("0.0.0.0:{}", port)).await;
|
||||
if let Err(e) = listener {
|
||||
log_message(format!("Failed to bind to port {}: {:?}", port, e));
|
||||
return false;
|
||||
}
|
||||
let listener = listener.unwrap();
|
||||
log_message(format!(
|
||||
"Barebones Server listening for HTTP and WS on 0.0.0.0:{}",
|
||||
port
|
||||
));
|
||||
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
match listener.accept().await {
|
||||
std::result::Result::Ok((stream, _addr)) => {
|
||||
let service = HttpService;
|
||||
tokio::spawn(async move {
|
||||
let io = TokioIo::new(stream);
|
||||
|
||||
if let Err(err) = http1::Builder::new()
|
||||
.preserve_header_case(true)
|
||||
.title_case_headers(true)
|
||||
.serve_connection(io, TowerToHyperService::new(service))
|
||||
.with_upgrades()
|
||||
.await
|
||||
{
|
||||
if let Some(io_err) =
|
||||
err.source().and_then(|e| e.downcast_ref::<io::Error>())
|
||||
{
|
||||
if io_err.kind() != io::ErrorKind::ConnectionReset
|
||||
&& io_err.kind() != io::ErrorKind::BrokenPipe
|
||||
{
|
||||
log_message(format!("Error serving connection: {:?}", err));
|
||||
}
|
||||
} else {
|
||||
log_message(format!("Error serving connection: {:?}", err));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
log_message(format!("Error accepting connection: {:?}", e));
|
||||
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
true
|
||||
}
|
||||
|
|
@ -1,16 +1,64 @@
|
|||
use crate::communities::{community_connection::CommunityConnection, community_manager};
|
||||
|
||||
use async_tungstenite::{WebSocketStream, accept_hdr_async, tungstenite::protocol::Message};
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use async_tungstenite::accept_hdr_async;
|
||||
use futures::StreamExt;
|
||||
use futures::stream::SplitSink;
|
||||
use futures::stream::SplitStream;
|
||||
use hyper::upgrade::Upgraded;
|
||||
use hyper_util::rt::TokioIo;
|
||||
use std::sync::Arc;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio_util::compat::{Compat, TokioAsyncReadCompatExt};
|
||||
use tungstenite::connect;
|
||||
use tungstenite::{
|
||||
Utf8Bytes,
|
||||
Message, Utf8Bytes,
|
||||
handshake::server::{Request, Response},
|
||||
};
|
||||
|
||||
pub fn handle(
|
||||
path: String,
|
||||
writer: SplitSink<tokio_tungstenite::WebSocketStream<TokioIo<Upgraded>>, Message>,
|
||||
reader: SplitStream<tokio_tungstenite::WebSocketStream<TokioIo<Upgraded>>>,
|
||||
) {
|
||||
tokio::spawn(async move {
|
||||
if path.starts_with("/ws/community/") {
|
||||
let community_id = path.split("/").nth(3).unwrap();
|
||||
if let Some(community) = community_manager::get_community(community_id).await {
|
||||
let community_conn: Arc<CommunityConnection> =
|
||||
Arc::from(CommunityConnection::new(writer, reader, community));
|
||||
loop {
|
||||
let msg_result = {
|
||||
let mut session_lock = community_conn.receiver.write().await;
|
||||
session_lock.next().await
|
||||
};
|
||||
|
||||
match msg_result {
|
||||
Some(Ok(msg)) => {
|
||||
if msg.is_text() {
|
||||
let text = msg.into_text().unwrap();
|
||||
community_conn
|
||||
.clone()
|
||||
.handle_message(text.to_string())
|
||||
.await;
|
||||
} else if msg.is_close() {
|
||||
community_conn.handle_close().await;
|
||||
return;
|
||||
}
|
||||
}
|
||||
Some(Err(_)) => {
|
||||
community_conn.handle_close().await;
|
||||
return;
|
||||
}
|
||||
None => {
|
||||
community_conn.handle_close().await;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub async fn start(port: u16) -> bool {
|
||||
let listener = TcpListener::bind(format!("0.0.0.0:{}", port)).await;
|
||||
if let Err(_) = listener {
|
||||
|
|
@ -19,56 +67,20 @@ pub async fn start(port: u16) -> bool {
|
|||
let listener = listener.unwrap();
|
||||
tokio::spawn(async move {
|
||||
while let Ok((stream, _)) = listener.accept().await {
|
||||
tokio::spawn(async move {
|
||||
let mut path: String = "/".to_string();
|
||||
let callback = |req: &Request, response: Response| {
|
||||
path = format!("{}", &req.uri().path());
|
||||
Ok(response)
|
||||
};
|
||||
let ws_stream = match accept_hdr_async(stream.compat(), callback).await {
|
||||
Ok(ws) => ws,
|
||||
Err(_) => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
let (reader, writer) = ws_stream.split();
|
||||
if path.starts_with("/community/") {
|
||||
let community_id = path.split("/").nth(2).unwrap();
|
||||
if let Some(community) = community_manager::get_community(community_id).await {
|
||||
let community_conn: Arc<CommunityConnection> =
|
||||
Arc::from(CommunityConnection::new(reader, writer, community));
|
||||
loop {
|
||||
let msg_result = {
|
||||
let mut session_lock = community_conn.receiver.write().await;
|
||||
session_lock.next().await
|
||||
};
|
||||
|
||||
match msg_result {
|
||||
Some(Ok(msg)) => {
|
||||
if msg.is_text() {
|
||||
let text = msg.into_text().unwrap();
|
||||
community_conn
|
||||
.clone()
|
||||
.handle_message(text.to_string())
|
||||
.await;
|
||||
} else if msg.is_close() {
|
||||
community_conn.handle_close().await;
|
||||
return;
|
||||
}
|
||||
}
|
||||
Some(Err(_)) => {
|
||||
community_conn.handle_close().await;
|
||||
return;
|
||||
}
|
||||
None => {
|
||||
community_conn.handle_close().await;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut path: String = "/".to_string();
|
||||
let callback = |req: &Request, response: Response| {
|
||||
path = format!("{}", &req.uri().path());
|
||||
Ok(response)
|
||||
};
|
||||
let ws_stream = match accept_hdr_async(stream.compat(), callback).await {
|
||||
Ok(ws) => ws,
|
||||
Err(_) => {
|
||||
return;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
let (reader, writer) = ws_stream.split();
|
||||
//handle(path, reader, writer);
|
||||
}
|
||||
});
|
||||
true
|
||||
|
|
|
|||
Loading…
Reference in a new issue