API, now with Post

This commit is contained in:
Alex Emmet 2025-11-22 21:19:25 +00:00
commit 3e119877b1
4 changed files with 158 additions and 114 deletions

View file

@ -2,19 +2,24 @@ use crate::gui::log_panel::log_message;
use crate::server::api;
use crate::server::socket::handle;
use crate::util::file_util::{load_file_buf, load_file_vec};
use base64::Engine;
use base64::engine::general_purpose::STANDARD;
use futures::{StreamExt, TryFutureExt};
use bytes::Bytes;
use futures::StreamExt;
use futures_util::TryFutureExt;
use http_body_util::Full;
use hyper::body::Bytes;
use http_body_util::{BodyExt, Collected};
use hyper::upgrade::OnUpgrade;
use hyper::{Method, StatusCode};
use hyper::{
Request as HttpRequest, Response as HttpResponse, StatusCode, body::Incoming,
server::conn::http1, upgrade,
Request as HttpRequest, Response as HttpResponse, body::Incoming, server::conn::http1, upgrade,
};
use hyper_util::rt::tokio::TokioIo;
use hyper_util::service::TowerToHyperService;
use rustls::ServerConfig;
use rustls::pki_types::{CertificateDer, PrivateKeyDer};
use sha1::digest::generic_array::arr::Inc;
use sha1::{Digest, Sha1};
use std::error::Error;
use std::io::ErrorKind;
@ -22,12 +27,12 @@ use std::io::{self, BufReader};
use std::net::{IpAddr, SocketAddr};
use std::result::Result::Ok;
use std::sync::Arc;
use std::thread::panicking;
use std::{future::Future, pin::Pin, time::Duration};
use tokio::net::TcpListener;
use tokio_rustls::TlsAcceptor;
use tokio_tungstenite::WebSocketStream;
use tower::Service;
#[derive(Clone)]
struct HttpService {
peer_addr: SocketAddr,
@ -46,21 +51,25 @@ impl Service<HttpRequest<Incoming>> for HttpService {
}
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 peer_ip = self.peer_addr.ip();
let is_local = is_local_network(peer_ip);
let (parts, body) = req.into_parts();
let method = parts.method.clone();
let path = parts.uri.path().to_string();
let headers = parts.headers.clone();
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")
{
let is_websocket_upgrade = path.starts_with("/ws")
&& method == Method::GET // WebSocket upgrades use GET
&& 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");
if is_websocket_upgrade {
log_message("Attempting WebSocket upgrade on /ws");
if let Some(sec_websocket_key) = headers.get("sec-websocket-key") {
@ -74,29 +83,30 @@ impl Service<HttpRequest<Incoming>> for HttpService {
.header("Sec-WebSocket-Accept", sec_websocket_accept)
.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 req_for_upgrade = HttpRequest::from_parts(parts, body);
let upgrades = upgrade::on(req_for_upgrade);
let handshake_result = WebSocketStream::from_raw_socket(
raw_stream,
tungstenite::protocol::Role::Server,
None,
)
.await;
log_message(format!("Handling WebSocket connection",));
let (writer, reader) = handshake_result.split();
handle(path, writer, reader);
}
Err(e) => {
log_message(format!(
"WebSocket upgrade failed after response: {:?}",
e
));
}
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;
log_message(format!("Handling WebSocket connection",));
let (writer, reader) = handshake_result.split();
handle(path.clone(), writer, reader);
}
});
Err(e) => {
log_message(format!(
"WebSocket upgrade failed after response: {:?}",
e
));
}
}
Ok(response)
} else {
log_message("No Sec-WebSocket-Key found in request headers");
@ -106,62 +116,80 @@ impl Service<HttpRequest<Incoming>> for HttpService {
.unwrap();
Ok(response)
}
} else if path.starts_with("/api") {
let whole_body = match body.collect().await {
Ok(collected) => collected,
Err(e) => {
log_message(format!("Error collecting body: {}", e));
return Ok(HttpResponse::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(Full::new(Bytes::from(format!(
"Failed to read body: {}",
e
))))
.unwrap());
}
};
let bytes = whole_body.to_bytes();
let body_string: Option<String> = match String::from_utf8(bytes.to_vec()) {
Ok(s) => Some(s),
Err(_) => None,
};
Ok(api::handle(&path, &is_local, headers.clone(), body_string).await)
} else {
if path.starts_with("/api") {
Ok(api::handle(&path, &is_local, headers).await)
let mut path_parts: Vec<&str> = path.split("/").collect();
let name = path_parts.remove(path_parts.len() - 1);
let name = if name.is_empty() {
"index.html"
} else if name.contains(".") && name.contains("?") {
name.split("?").next().unwrap()
} else if name.contains(".") {
name
} else {
let mut path_parts: Vec<&str> = path.split("/").collect();
let name = path_parts.remove(path_parts.len() - 1);
let name = if name.is_empty() {
"index.html"
} else if name.contains(".") && name.contains("?") {
name.split("?").next().unwrap()
} else if name.contains(".") {
name
} else {
&format!("{}.html", name)
};
let code = if let Some(ext) = name.split(".").last() {
match ext {
"html" => "text/html",
"css" => "text/css",
"ico" => "image/x-icon",
"png" => "image/png",
"js" => "application/javascript",
"json" => "application/json",
_ => "application/octet-stream",
}
} else {
"application/octet-stream"
};
let (status, content, body_text): (StatusCode, &str, Vec<u8>) = {
let content = load_file_vec(&format!("web{}/", path_parts.join("/")), name);
&format!("{}.html", name)
};
let code = if let Some(ext) = name.split(".").last() {
match ext {
"html" => "text/html",
"css" => "text/css",
"ico" => "image/x-icon",
"png" => "image/png",
"js" => "application/javascript",
"json" => "application/json",
_ => "application/octet-stream",
}
} else {
"application/octet-stream"
};
let (status, content, body_text): (StatusCode, &str, Vec<u8>) = {
let content = load_file_vec(&format!("web{}/", path_parts.join("/")), name);
if content.is_empty() {
let content = load_file_vec("web", "404.html");
if content.is_empty() {
let content = load_file_vec("web", "404.html");
if content.is_empty() {
(
StatusCode::NOT_FOUND,
code,
include_str!("../../static/web/404.html")
.as_bytes()
.to_vec(),
)
} else {
(StatusCode::OK, code, content)
}
(
StatusCode::NOT_FOUND,
code,
include_str!("../../static/web/404.html")
.as_bytes()
.to_vec(),
)
} else {
(StatusCode::OK, code, content)
}
};
} else {
(StatusCode::OK, code, content)
}
};
let body = Full::new(Bytes::from(body_text.to_vec()));
let response = HttpResponse::builder()
.header("Content-Type", content)
.status(status)
.body(body)
.unwrap();
Ok(response)
}
let body = Full::new(Bytes::from(body_text.to_vec()));
let response = HttpResponse::builder()
.header("Content-Type", content)
.status(status)
.body(body)
.unwrap();
Ok(response)
}
};