forked from emilk/eframe_template
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implemented REST APIs for sync calls rather than async events
- Loading branch information
1 parent
a305c16
commit 0cf5685
Showing
64 changed files
with
3,895 additions
and
1,071 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
use http::{HeaderName, StatusCode}; | ||
use http_body_util::Full; | ||
use hyper::{body::Bytes, Request, Response}; | ||
use include_dir::{include_dir, Dir}; | ||
|
||
const HTML_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/../../crates/web/dist"); | ||
|
||
pub async fn get_handler( | ||
req: Request<hyper::body::Incoming>, | ||
) -> anyhow::Result<Response<Full<Bytes>>> { | ||
// Get the path to the thing we loading and sanitize it | ||
let mut path = req.uri().path(); | ||
|
||
// Special case and strip slash | ||
if path.is_empty() || path == "/" { | ||
path = "/index.html"; | ||
} | ||
if path.starts_with("/") { | ||
path = &path[1..]; | ||
} | ||
|
||
// Sanitize | ||
if path.contains("..") || path.starts_with("/") || path.starts_with("~") { | ||
tracing::warn!("Access denied: path={}", path); | ||
return Ok(Response::builder() | ||
.status(StatusCode::FORBIDDEN) | ||
.body(Full::new(Bytes::from("Access denied")))?); | ||
} | ||
|
||
// Load the file | ||
let file = match HTML_DIR.get_file(path) { | ||
Some(file) => file, | ||
None => { | ||
tracing::debug!("Not found: path={}", path); | ||
return Ok(Response::builder() | ||
.status(StatusCode::NOT_FOUND) | ||
.body(Full::new(Bytes::from("File not found")))?); | ||
} | ||
}; | ||
|
||
// Cache time | ||
let cache_control = if file.path().ends_with(".mp3") | ||
|| file.path().ends_with(".mp4") | ||
|| file.path().ends_with(".wav") | ||
|| file.path().ends_with(".wasm") | ||
{ | ||
"Cache-Control: max-age=86400, stale-while-revalidate=86400" | ||
} else { | ||
"Cache-Control: max-age=30, stale-while-revalidate=86400" | ||
}; | ||
|
||
// Write the response | ||
let mut res = Response::new(Full::new(Bytes::from(file.contents()))); | ||
|
||
let meme = mime_guess::from_path(file.path()).first_or_octet_stream(); | ||
res.headers_mut().insert( | ||
http::header::CONNECTION, | ||
http::HeaderValue::from_str("Keep-Alive")?, | ||
); | ||
res.headers_mut().insert( | ||
HeaderName::from_static("Keep-Alive"), | ||
http::HeaderValue::from_str("timeout=2, max=100")?, | ||
); | ||
/* | ||
res.headers_mut().insert( | ||
http::header::ACCESS_CONTROL_ALLOW_ORIGIN, | ||
http::HeaderValue::from_str("*")?, | ||
); | ||
*/ | ||
res.headers_mut().insert( | ||
http::header::CONTENT_TYPE, | ||
http::HeaderValue::from_str(&meme.to_string())?, | ||
); | ||
res.headers_mut().insert( | ||
http::header::CACHE_CONTROL, | ||
http::HeaderValue::from_str(cache_control)?, | ||
); | ||
|
||
tracing::debug!("Response: status={}, url={}", res.status(), req.uri()); | ||
Ok(res) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
use http::{Method, StatusCode}; | ||
use http_body_util::Full; | ||
use hyper::{body::Bytes, Request, Response}; | ||
use immutable_bank_model::bank_id::BankId; | ||
|
||
use crate::{ | ||
general_state::GeneralState, | ||
handlers::{get::get_handler, options::options_handler, post::post_handler, ws::handle_ws}, | ||
}; | ||
|
||
pub async fn http_handler( | ||
mut req: Request<hyper::body::Incoming>, | ||
state: GeneralState, | ||
) -> anyhow::Result<Response<Full<Bytes>>> { | ||
tracing::debug!("Request: method={}, url={}", req.method(), req.uri()); | ||
|
||
if req.method() == &Method::OPTIONS { | ||
return options_handler(req).await; | ||
} | ||
|
||
if hyper_tungstenite::is_upgrade_request(&req) { | ||
tracing::debug!("Request: upgrading to websocket"); | ||
let (response, websocket) = hyper_tungstenite::upgrade(&mut req, None)?; | ||
|
||
// Get the bank ID from the request query string | ||
let bank_path = req.uri().path().to_lowercase(); | ||
let bank_id = match bank_path.split_once("/bank/") { | ||
Some((_, bank_id)) => BankId::from(bank_id), | ||
None => { | ||
return Ok(Response::builder() | ||
.status(StatusCode::BAD_REQUEST) | ||
.body(Full::new(Bytes::from("Invalid Request")))?); | ||
} | ||
}; | ||
|
||
// Spawn a task to handle the websocket connection. | ||
tokio::spawn(async move { | ||
if let Err(e) = handle_ws(websocket, bank_id, state).await { | ||
tracing::error!("Error in websocket connection: {e}"); | ||
} | ||
}); | ||
|
||
// Return the response so the spawned future can continue. | ||
return Ok(response); | ||
} | ||
|
||
// If its a GET request | ||
if req.method() == Method::GET { | ||
return get_handler(req).await; | ||
} | ||
|
||
// Maybe its a API call | ||
if req.method() == Method::POST { | ||
return post_handler(req, state).await; | ||
} | ||
|
||
Ok(Response::builder() | ||
.status(StatusCode::BAD_REQUEST) | ||
.body(Full::new(Bytes::from("Invalid Request")))?) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
|
||
pub mod ws; | ||
pub mod http; | ||
pub mod get; | ||
pub mod post; | ||
pub mod options; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
use http::StatusCode; | ||
use http_body_util::Full; | ||
use hyper::{body::Bytes, Request, Response}; | ||
|
||
pub async fn options_handler( | ||
req: Request<hyper::body::Incoming>, | ||
) -> anyhow::Result<Response<Full<Bytes>>> { | ||
let res = Response::builder() | ||
.status(StatusCode::OK) | ||
.header("Access-Control-Allow-Origin", "*") | ||
.header("Access-Control-Allow-Headers", "*") | ||
.header("Access-Control-Allow-Methods", "POST, GET, OPTIONS") | ||
.body(Full::default())?; | ||
|
||
tracing::debug!("Response: status={}, url={}", res.status(), req.uri()); | ||
Ok(res) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
use bytes::Bytes; | ||
use http::{Request, Response, StatusCode}; | ||
use http_body_util::{BodyExt, Full}; | ||
|
||
use crate::{general_state::GeneralState, BROKER_SECRET}; | ||
|
||
// Sending more than a megabyte is prevented | ||
pub const MAX_REQUEST_BODY: usize = 1024 * 1024; | ||
|
||
pub async fn post_handler( | ||
req: Request<hyper::body::Incoming>, | ||
state: GeneralState, | ||
) -> anyhow::Result<Response<Full<Bytes>>> { | ||
let (parts, mut body) = req.into_parts(); | ||
|
||
// Read all the data to a particular limit and then fail | ||
// (this is to prevent DDOS attacks) | ||
let mut data: Vec<u8> = Vec::with_capacity(4906); | ||
while let Some(frame) = body.frame().await { | ||
if let Some(frame) = frame?.data_ref() { | ||
if data.len() > frame.len() { | ||
return Ok(Response::builder() | ||
.status(StatusCode::FORBIDDEN) | ||
.body(Full::new(Bytes::from("DDOS protection")))?); | ||
} | ||
data.extend_from_slice(frame); | ||
} | ||
} | ||
|
||
// pattern match for both the method and the path of the request | ||
let res = match (parts.method, parts.uri.path()) { | ||
(hyper::Method::POST, "/update-bank") => { | ||
let req = serde_json::from_slice(&data)?; | ||
let state_inner = state.clone(); | ||
let res = state | ||
.inner | ||
.lock() | ||
.await | ||
.ledger | ||
.update_bank(req, move |msg| state_inner.broadcast(msg))?; | ||
tracing::info!("UpdateBank-Response: {:?}", res); | ||
serde_json::to_vec_pretty(&res)? | ||
} | ||
(hyper::Method::POST, "/new-bank") => { | ||
let req = serde_json::from_slice(&data)?; | ||
let state_inner = state.clone(); | ||
let res = | ||
state | ||
.inner | ||
.lock() | ||
.await | ||
.ledger | ||
.new_bank(&BROKER_SECRET, req, move |msg| state_inner.broadcast(msg))?; | ||
tracing::info!("NewBank-Response: {:?}", res); | ||
serde_json::to_vec_pretty(&res)? | ||
} | ||
(hyper::Method::POST, "/transfer") => { | ||
let req = serde_json::from_slice(&data)?; | ||
let state_inner = state.clone(); | ||
let res = state | ||
.inner | ||
.lock() | ||
.await | ||
.ledger | ||
.transfer(req, move |msg| state_inner.broadcast(msg))?; | ||
tracing::info!("Transfer-Response: {:?}", res); | ||
serde_json::to_vec_pretty(&res)? | ||
} | ||
// Anything else handler | ||
_ => { | ||
return Ok(Response::builder() | ||
.status(StatusCode::BAD_REQUEST) | ||
.body(Full::new(Bytes::from("Invalid Request")))?) | ||
} | ||
}; | ||
|
||
Ok(Response::builder() | ||
.status(StatusCode::OK) | ||
.header("Access-Control-Allow-Origin", "*") | ||
.header("Access-Control-Allow-Headers", "*") | ||
.header("Access-Control-Allow-Methods", "POST, GET, OPTIONS") | ||
.header(http::header::CONTENT_TYPE, "application/json") | ||
.body(Full::new(Bytes::from(res)))?) | ||
} |
Oops, something went wrong.