summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLibravatarLarge Libravatar memdmp <memdmpestrogenzone>2025-05-18 11:41:01 +0200
committerLibravatarLarge Libravatar memdmp <memdmpestrogenzone>2025-05-18 11:41:01 +0200
commitbfb995563f41b86de2f0a82999c99391f7f00582 (patch)
tree56fc70e4e31d07237f4f3bb76cf9d56ef4d09681 /src
downloadspaceapi-server-bfb995563f41b86de2f0a82999c99391f7f00582.tar.gz
spaceapi-server-bfb995563f41b86de2f0a82999c99391f7f00582.tar.bz2
spaceapi-server-bfb995563f41b86de2f0a82999c99391f7f00582.tar.lz
spaceapi-server-bfb995563f41b86de2f0a82999c99391f7f00582.zip

feat: initial commit

Diffstat (limited to 'src')
-rw-r--r--src/main.rs176
1 files changed, 176 insertions, 0 deletions
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..7c63c09
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,176 @@
+use std::env;
+use std::io::Error;
+use std::net::SocketAddr;
+use std::sync::LazyLock;
+use std::time::Duration;
+
+use http_body_util::{BodyExt, Empty, Full};
+use hyper::Uri;
+use hyper::body::Incoming;
+use hyper::header::HeaderValue;
+use hyper::{Request, Response, body::Bytes, server::conn::http1, service::service_fn};
+use hyper_util::rt::TokioIo;
+use serde_json::json;
+use tokio::net::TcpStream;
+use tokio::{net::TcpListener, sync::RwLock, task, time};
+
+static RAW_JSON: &'static str = include_str!("../api.json");
+static PARSED_JSON: LazyLock<RwLock<serde_json::Value>> =
+ LazyLock::new(|| RwLock::new(serde_json::from_str(RAW_JSON).expect("JSON parse error")));
+
+type StdResult<T, E> = std::result::Result<T, E>;
+type Result<T> = StdResult<T, Box<dyn std::error::Error + Send + Sync>>;
+
+async fn respond(_: Request<hyper::body::Incoming>) -> Result<Response<Full<Bytes>>> {
+ let str = PARSED_JSON.read().await.to_string();
+ let len = str.len();
+ let mut res = Response::new(Full::new(Bytes::from(str)));
+ res
+ .headers_mut()
+ .append("Content-Type", HeaderValue::from_static("application/json"));
+ let len = HeaderValue::from_str(&len.to_string());
+ if len.is_ok() {
+ res.headers_mut().append("Content-Length", len.unwrap());
+ }
+ Ok(res)
+}
+async fn fetch_url(url: hyper::Uri) -> Result<Response<Incoming>> {
+ if url.scheme_str() != Some("http") {
+ return Err(Box::new(Error::new(
+ std::io::ErrorKind::InvalidInput,
+ "This function only works with HTTP URIs.",
+ )));
+ }
+
+ let host = url.host().expect("uri has no host");
+ let port = url.port_u16().unwrap_or(80);
+ let addr = format!("{}:{}", host, port);
+ let stream = TcpStream::connect(addr).await?;
+ let io = TokioIo::new(stream);
+
+ let (mut sender, conn) = hyper::client::conn::http1::handshake(io).await?;
+ tokio::task::spawn(async move {
+ if let Err(err) = conn.await {
+ println!("Connection failed: {:?}", err);
+ }
+ });
+
+ let authority = url.authority().unwrap().clone();
+
+ let path = url.path();
+ let req = Request::builder()
+ .uri(path)
+ .header(hyper::header::HOST, authority.as_str())
+ .body(Empty::<Bytes>::new())?;
+
+ Ok(sender.send_request(req).await?)
+}
+fn get_request_target_uri() -> Uri {
+ env::var("REQUEST_TO")
+ .unwrap_or("http://10.0.0.77:8080/".to_string())
+ .parse::<hyper::Uri>()
+ .unwrap()
+}
+async fn get_is_open() -> Result<bool> {
+ let mut res = fetch_url(get_request_target_uri()).await?;
+
+ let mut out = "".to_string();
+ while let Some(next) = res.frame().await {
+ let frame = next?;
+ if let Some(chunk) = frame.data_ref() {
+ out = format!("{out}{}", std::str::from_utf8(chunk).unwrap());
+ }
+ }
+
+ let state = out.trim().starts_with("a");
+
+ Ok(state)
+}
+
+#[tokio::main]
+async fn main() -> Result<()> {
+ {
+ let state = {
+ let mut parsed_json = PARSED_JSON.write().await;
+ if !parsed_json.is_object() {
+ panic!("api.json must have top-level object!");
+ }
+ let parsed_json_obj = parsed_json.as_object_mut().unwrap();
+ let state = parsed_json_obj.get_mut("state");
+ let state = if state.is_some() {
+ state.unwrap()
+ } else {
+ &mut json!("{}")
+ };
+ let state_obj = state.as_object_mut().unwrap();
+ state_obj.remove_entry("open");
+ state.clone()
+ };
+
+ // ensure state is present
+ let mut parsed_json = PARSED_JSON.write().await;
+ let parsed_json_obj = parsed_json.as_object_mut().unwrap();
+ parsed_json_obj.insert("state".to_string(), state);
+ }
+ task::spawn(async {
+ loop {
+ {
+ let is_open = get_is_open().await;
+
+ let mut parsed_json = PARSED_JSON.write().await;
+ let state = parsed_json.get_mut("state");
+ if state.is_none() {
+ panic!("State is none!");
+ };
+ let state = state.unwrap().as_object_mut();
+ if state.is_none() {
+ panic!("State was turned into non-object!");
+ }
+ let state = state.unwrap();
+
+ if is_open.is_ok() {
+ let is_open = is_open.unwrap();
+ state.insert("open".to_string(), serde_json::Value::Bool(is_open));
+ } else {
+ state.remove("open");
+ eprintln!("Failed to fetch open status: {:#?}", is_open.unwrap_err());
+ }
+ }
+ time::sleep(Duration::from_secs(1)).await;
+ }
+ });
+
+ let addr: SocketAddr = env::var("LISTEN_ON")
+ .unwrap_or("127.0.0.1:3000".to_string())
+ .parse()?;
+
+ // We create a TcpListener and bind it to 127.0.0.1:3000
+ let listener = TcpListener::bind(addr).await?;
+
+ println!(
+ "Listening on: LISTEN_ON={addr:#?}
+Sending fetch requests to: REQUEST_TO={:#?}",
+ get_request_target_uri()
+ );
+
+ // We start a loop to continuously accept incoming connections
+ loop {
+ let (stream, _) = listener.accept().await?;
+
+ // Use an adapter to access something implementing `tokio::io` traits as if they implement
+ // `hyper::rt` IO traits.
+ let io = TokioIo::new(stream);
+
+ // Spawn a tokio task to serve multiple connections concurrently
+ tokio::task::spawn(async move {
+ // Finally, we bind the incoming connection to our `hello` service
+ if let Err(err) = http1::Builder::new()
+ // `service_fn` converts our function in a `Service`
+ .serve_connection(io, service_fn(respond))
+ .await
+ {
+ eprintln!("Error serving connection: {:?}", err);
+ }
+ });
+ }
+}