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

4
Cargo.lock generated
View file

@ -1169,9 +1169,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "1.8.0" version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1744436df46f0bde35af3eda22aeaba453aada65d8f1c171cd8a5f59030bd69f" checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11"
dependencies = [ dependencies = [
"atomic-waker", "atomic-waker",
"bytes", "bytes",

View file

@ -17,8 +17,8 @@ futures-util = "*"
hex = "*" hex = "*"
hkdf = "*" hkdf = "*"
hmac = "*" hmac = "*"
hyper = { version = "*", features = ["full"] } hyper-util = { version = "*" }
hyper-util = "*" hyper = { version = "1.8.1", features = ["capi", "client", "full", "http1", "http2", "nightly", "server"] }
http-body-util = "*" http-body-util = "*"
json = "*" json = "*"
native-tls = { version = "*", default-features = false } native-tls = { version = "*", default-features = false }

View file

@ -17,6 +17,7 @@ pub async fn handle(
path: &str, path: &str,
is_local: &bool, is_local: &bool,
headers: HeaderMap<HeaderValue>, headers: HeaderMap<HeaderValue>,
body_string: Option<String>,
) -> HttpResponse<Full<Bytes>> { ) -> HttpResponse<Full<Bytes>> {
if !is_local { if !is_local {
return HttpResponse::builder() return HttpResponse::builder()
@ -26,7 +27,7 @@ pub async fn handle(
} }
let path_parts: Vec<&str> = path.split("/").collect(); let path_parts: Vec<&str> = path.split("/").collect();
let body: Option<JsonValue> = body_string.map(|s| json::parse(&s).unwrap());
let (status, content, body_text) = if path_parts.len() >= 3 { let (status, content, body_text) = if path_parts.len() >= 3 {
match path_parts[2] { match path_parts[2] {
"app_state" => (StatusCode::OK, "application/json", { "app_state" => (StatusCode::OK, "application/json", {
@ -46,26 +47,33 @@ pub async fn handle(
if path_parts.len() >= 4 { if path_parts.len() >= 4 {
match path_parts[3] { match path_parts[3] {
"add" => { "add" => {
let username = headers.get("username"); if body.is_none() {
if let (Some(user), Some(_private_key)) = user_manager::create_user( "{\"type\":\"error\"}".to_string()
&username.unwrap().to_str().unwrap().to_string(), } else {
) let username =
.await body.unwrap()["username"].as_str().unwrap().to_string();
if let (Some(user), Some(_private_key)) =
user_manager::create_user(&username).await
{ {
let cv = CommunicationValue::new(CommunicationType::create_user) let cv =
CommunicationValue::new(CommunicationType::create_user)
.add_data(DataTypes::user, user.frontend()); .add_data(DataTypes::user, user.frontend());
cv.to_json().to_string() cv.to_json().to_string()
} else { } else {
"{\"type\":\"error\"}".to_string() "{\"type\":\"error\"}".to_string()
} }
} }
}
"remove" => { "remove" => {
let uuid = if body.is_none() {
Uuid::parse_str(headers.get("uuid").unwrap().to_str().unwrap()) "{\"type\":\"error\"}".to_string()
} else {
let uuid = Uuid::parse_str(body.unwrap()["uuid"].as_str().unwrap())
.unwrap(); .unwrap();
user_manager::remove_user(uuid); user_manager::remove_user(uuid);
"{}".to_string() "{}".to_string()
} }
}
"get" => { "get" => {
let users = user_manager::get_users(); let users = user_manager::get_users();
let mut json = JsonValue::new_array(); let mut json = JsonValue::new_array();
@ -89,16 +97,24 @@ pub async fn handle(
if path_parts.len() >= 4 { if path_parts.len() >= 4 {
match path_parts[3] { match path_parts[3] {
"add" => { "add" => {
let name = headers.get("name").unwrap().to_str().unwrap(); if body.is_none() {
let community = Arc::new(Community::create(name.to_string()).await); "{\"type\":\"error\"}".to_string()
} else {
let name = body.unwrap()["name"].as_str().unwrap().to_string();
let community = Arc::new(Community::create(name).await);
community_manager::add_community(community).await; community_manager::add_community(community).await;
"{\"type\":\"success\"}".to_string() "{\"type\":\"success\"}".to_string()
} }
}
"remove" => { "remove" => {
let name = headers.get("name").unwrap().to_str().unwrap(); if body.is_none() {
community_manager::remove_community(name).await; "{\"type\":\"error\"}".to_string()
} else {
let name = body.unwrap()["name"].as_str().unwrap().to_string();
community_manager::remove_community(&name).await;
"{\"type\":\"success\"}".to_string() "{\"type\":\"success\"}".to_string()
} }
}
"get" => { "get" => {
let communities = community_manager::get_communities().await; let communities = community_manager::get_communities().await;
let mut json = JsonValue::new_array(); let mut json = JsonValue::new_array();

View file

@ -2,19 +2,24 @@ use crate::gui::log_panel::log_message;
use crate::server::api; use crate::server::api;
use crate::server::socket::handle; use crate::server::socket::handle;
use crate::util::file_util::{load_file_buf, load_file_vec}; use crate::util::file_util::{load_file_buf, load_file_vec};
use base64::Engine; use base64::Engine;
use base64::engine::general_purpose::STANDARD; 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 http_body_util::Full;
use hyper::body::Bytes; use http_body_util::{BodyExt, Collected};
use hyper::upgrade::OnUpgrade;
use hyper::{Method, StatusCode};
use hyper::{ use hyper::{
Request as HttpRequest, Response as HttpResponse, StatusCode, body::Incoming, Request as HttpRequest, Response as HttpResponse, body::Incoming, server::conn::http1, upgrade,
server::conn::http1, upgrade,
}; };
use hyper_util::rt::tokio::TokioIo; use hyper_util::rt::tokio::TokioIo;
use hyper_util::service::TowerToHyperService; use hyper_util::service::TowerToHyperService;
use rustls::ServerConfig; use rustls::ServerConfig;
use rustls::pki_types::{CertificateDer, PrivateKeyDer}; use rustls::pki_types::{CertificateDer, PrivateKeyDer};
use sha1::digest::generic_array::arr::Inc;
use sha1::{Digest, Sha1}; use sha1::{Digest, Sha1};
use std::error::Error; use std::error::Error;
use std::io::ErrorKind; use std::io::ErrorKind;
@ -22,12 +27,12 @@ use std::io::{self, BufReader};
use std::net::{IpAddr, SocketAddr}; use std::net::{IpAddr, SocketAddr};
use std::result::Result::Ok; use std::result::Result::Ok;
use std::sync::Arc; use std::sync::Arc;
use std::thread::panicking;
use std::{future::Future, pin::Pin, time::Duration}; use std::{future::Future, pin::Pin, time::Duration};
use tokio::net::TcpListener; use tokio::net::TcpListener;
use tokio_rustls::TlsAcceptor; use tokio_rustls::TlsAcceptor;
use tokio_tungstenite::WebSocketStream; use tokio_tungstenite::WebSocketStream;
use tower::Service; use tower::Service;
#[derive(Clone)] #[derive(Clone)]
struct HttpService { struct HttpService {
peer_addr: SocketAddr, peer_addr: SocketAddr,
@ -46,21 +51,25 @@ impl Service<HttpRequest<Incoming>> for HttpService {
} }
fn call(&mut self, req: HttpRequest<Incoming>) -> Self::Future { 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 peer_ip = self.peer_addr.ip();
let is_local = is_local_network(peer_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 { let fut = async move {
if path.starts_with("/ws") let is_websocket_upgrade = path.starts_with("/ws")
&& method == Method::GET // WebSocket upgrades use GET
&& headers && headers
.get("connection") .get("connection")
.map(|v| v.to_str().unwrap_or("").contains("Upgrade")) .map(|v| v.to_str().unwrap_or("").contains("Upgrade"))
.unwrap_or(false) .unwrap_or(false)
&& headers.get("upgrade").map(|v| v.to_str().unwrap_or("")) == Some("websocket") && headers.get("upgrade").map(|v| v.to_str().unwrap_or("")) == Some("websocket");
{
if is_websocket_upgrade {
log_message("Attempting WebSocket upgrade on /ws"); log_message("Attempting WebSocket upgrade on /ws");
if let Some(sec_websocket_key) = headers.get("sec-websocket-key") { if let Some(sec_websocket_key) = headers.get("sec-websocket-key") {
@ -74,7 +83,9 @@ impl Service<HttpRequest<Incoming>> for HttpService {
.header("Sec-WebSocket-Accept", sec_websocket_accept) .header("Sec-WebSocket-Accept", sec_websocket_accept)
.body(Full::new(Bytes::from(""))) .body(Full::new(Bytes::from("")))
.unwrap(); .unwrap();
tokio::spawn(async move { let req_for_upgrade = HttpRequest::from_parts(parts, body);
let upgrades = upgrade::on(req_for_upgrade);
match upgrades.await { match upgrades.await {
std::result::Result::Ok(upgraded_stream) => { std::result::Result::Ok(upgraded_stream) => {
let raw_stream = TokioIo::new(upgraded_stream); let raw_stream = TokioIo::new(upgraded_stream);
@ -87,7 +98,7 @@ impl Service<HttpRequest<Incoming>> for HttpService {
.await; .await;
log_message(format!("Handling WebSocket connection",)); log_message(format!("Handling WebSocket connection",));
let (writer, reader) = handshake_result.split(); let (writer, reader) = handshake_result.split();
handle(path, writer, reader); handle(path.clone(), writer, reader);
} }
Err(e) => { Err(e) => {
log_message(format!( log_message(format!(
@ -96,7 +107,6 @@ impl Service<HttpRequest<Incoming>> for HttpService {
)); ));
} }
} }
});
Ok(response) Ok(response)
} else { } else {
log_message("No Sec-WebSocket-Key found in request headers"); log_message("No Sec-WebSocket-Key found in request headers");
@ -106,9 +116,28 @@ impl Service<HttpRequest<Incoming>> for HttpService {
.unwrap(); .unwrap();
Ok(response) Ok(response)
} }
} else { } else if path.starts_with("/api") {
if path.starts_with("/api") { let whole_body = match body.collect().await {
Ok(api::handle(&path, &is_local, headers).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 { } else {
let mut path_parts: Vec<&str> = path.split("/").collect(); let mut path_parts: Vec<&str> = path.split("/").collect();
let name = path_parts.remove(path_parts.len() - 1); let name = path_parts.remove(path_parts.len() - 1);
@ -162,7 +191,6 @@ impl Service<HttpRequest<Incoming>> for HttpService {
.unwrap(); .unwrap();
Ok(response) Ok(response)
} }
}
}; };
Box::pin(fut.map_err(|err: color_eyre::eyre::ErrReport| { Box::pin(fut.map_err(|err: color_eyre::eyre::ErrReport| {