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
|
|
@ -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