API, now with Post
This commit is contained in:
parent
ec0a6311f1
commit
3e119877b1
4 changed files with 158 additions and 114 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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| {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue