You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
205 lines
4.7 KiB
Rust
205 lines
4.7 KiB
Rust
mod api;
|
|
mod util;
|
|
|
|
use std::{collections::BTreeMap, net::ToSocketAddrs};
|
|
|
|
use flate2::Compression;
|
|
use hyper::{
|
|
header::HeaderValue,
|
|
service::{make_service_fn, service_fn},
|
|
Body, Request as Request_, Response as Response_,
|
|
};
|
|
use serde::{Deserialize, Serialize};
|
|
use tower::ServiceBuilder;
|
|
|
|
type Request = Request_<Body>;
|
|
type Response = Response_<String>;
|
|
|
|
pub type Result<T> = core::result::Result<T, std::io::Error>;
|
|
|
|
#[derive(Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct HttpService {
|
|
url: String,
|
|
public_url: Option<String>,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct ConfigSchema {
|
|
listen_addr: Option<String>,
|
|
title: Option<String>,
|
|
description: Option<String>,
|
|
hosts: BTreeMap<String, String>,
|
|
services: BTreeMap<String, HttpService>,
|
|
}
|
|
|
|
pub struct Config {
|
|
listen_addr: String,
|
|
title: String,
|
|
description: String,
|
|
hosts: BTreeMap<String, String>,
|
|
services: BTreeMap<String, HttpService>,
|
|
}
|
|
|
|
fn not_found() -> Result<Response> {
|
|
Ok(Response_::builder()
|
|
.status(404)
|
|
.body("Not found".into())
|
|
.unwrap())
|
|
}
|
|
|
|
fn internal_server_error() -> Result<Response> {
|
|
Ok(Response_::builder()
|
|
.status(500)
|
|
.body("Internal service erorr".into())
|
|
.unwrap())
|
|
}
|
|
|
|
async fn index() -> Result<Response> {
|
|
let config = read_config().await?;
|
|
|
|
let mut resp = Response::new(format!(
|
|
r###"
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<meta name="description" content="{description}">
|
|
<script type="module">
|
|
{script}
|
|
</script>
|
|
<style>
|
|
{style}
|
|
</style>
|
|
<title>{title}</title>
|
|
</head>
|
|
<body>
|
|
<noscript>
|
|
You need to enable JavaScript to view this page.
|
|
</noscript>
|
|
</body>
|
|
</html>
|
|
"###,
|
|
title = config.title,
|
|
description = config.description,
|
|
style = include_str!("../frontend/style.css"),
|
|
script = include_str!(concat!(env!("OUT_DIR"), "/frontend.js")),
|
|
));
|
|
resp.headers_mut().insert(
|
|
"content-type",
|
|
HeaderValue::from_static("text/html; charset=utf-8"),
|
|
);
|
|
|
|
Ok(resp)
|
|
}
|
|
|
|
fn json<T: Serialize>(v: &T) -> Result<Response> {
|
|
let json = serde_json::to_string(&v)?;
|
|
|
|
let mut resp = Response::new(json);
|
|
resp.headers_mut()
|
|
.insert("content-type", HeaderValue::from_static("application/json"));
|
|
|
|
Ok(resp)
|
|
}
|
|
|
|
async fn route(req: Request) -> Result<Response> {
|
|
let parts = req.uri().path().split('/').skip(1).collect::<Vec<_>>();
|
|
|
|
let resp = match parts.as_slice() {
|
|
["api", "v1", ref rest @ ..] => match rest {
|
|
["configuration", "get"] => {
|
|
json(&api::v1::get_configuration().await?)?
|
|
}
|
|
["host", "getStatus"] => json(
|
|
&api::v1::get_host_status(serde_json::from_slice(
|
|
&hyper::body::to_bytes(req.into_body()).await.unwrap(),
|
|
)?)
|
|
.await?,
|
|
)?,
|
|
["service", "getStatus"] => json(
|
|
&api::v1::get_service_status(serde_json::from_slice(
|
|
&hyper::body::to_bytes(req.into_body()).await.unwrap(),
|
|
)?)
|
|
.await?,
|
|
)?,
|
|
_ => return not_found(),
|
|
},
|
|
[""] => index().await?,
|
|
_ => return not_found(),
|
|
};
|
|
|
|
Ok(resp)
|
|
}
|
|
|
|
pub async fn read_config() -> Result<Config> {
|
|
let config = tokio::fs::read_to_string("./config.json").await?;
|
|
let parsed: ConfigSchema = serde_json::from_str(&config)?;
|
|
|
|
Ok(Config {
|
|
listen_addr: parsed
|
|
.listen_addr
|
|
.unwrap_or_else(|| "127.0.0.1:8888".into()),
|
|
title: parsed.title.clone().unwrap_or_else(|| "Statue".into()),
|
|
description: match parsed.description {
|
|
Some(d) => d,
|
|
None => match parsed.title {
|
|
Some(t) => t,
|
|
None => "Statue - a simple status monitor".into(),
|
|
},
|
|
},
|
|
hosts: parsed.hosts,
|
|
services: parsed.services,
|
|
})
|
|
}
|
|
|
|
async fn serve() -> Result<()> {
|
|
let config = read_config().await?;
|
|
let addr = config.listen_addr.to_socket_addrs()?.next().unwrap();
|
|
let service = service_fn(|req| async {
|
|
let resp = route(req).await;
|
|
|
|
match resp {
|
|
Ok(r) => Ok(r),
|
|
Err(e) => {
|
|
eprintln!("{}", e);
|
|
|
|
internal_server_error()
|
|
}
|
|
}
|
|
});
|
|
let make_service = make_service_fn(|_conn| async move {
|
|
let service = ServiceBuilder::new()
|
|
.layer(util::Log)
|
|
.layer(util::Compress {
|
|
algorithms: util::AlgorithmPreferences {
|
|
brotli: Some((3, util::brotli_default_params(4, true))),
|
|
gzip: Some((2, Compression::fast())),
|
|
deflate: Some((1, Compression::fast())),
|
|
},
|
|
predicate: util::CompressPredicate {
|
|
min_size: 256,
|
|
content_type_predicate: |_: &str| true,
|
|
},
|
|
})
|
|
.service(service);
|
|
|
|
Ok::<_, std::convert::Infallible>(service)
|
|
});
|
|
let server = hyper::server::Server::bind(&addr).serve(make_service);
|
|
|
|
println!("Listening on {}", addr);
|
|
|
|
if let Err(e) = server.await {
|
|
eprintln!("server error: {}", e);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
serve().await.unwrap();
|
|
}
|