2024-08-01 13:21:20 +02:00

247 lines
7.3 KiB
Rust

mod services;
use crate::services::Service;
mod config;
use crate::config::Config;
use actix_files::Files;
use actix_governor::{Governor, GovernorConfigBuilder, KeyExtractor, SimpleKeyExtractionError};
use actix_web::{get, web,
http::{
header::ContentType,
StatusCode,
// Method, StatusCode,
},
//cookie::{ Key, SameSite, Cookie },
App, /*HttpRequest,*/ HttpServer, HttpResponse, Responder, HttpResponseBuilder
};
use actix_web::dev::ServiceRequest;
use governor::{NotUntil, clock::{Clock, QuantaInstant, DefaultClock}};
use awc::Client;
use serde_json::{Map, Value};
use handlebars::Handlebars;
use handlebars::handlebars_helper;
use tokio_xmpp::SimpleClient as XmppClient;
#[macro_use]
extern crate lazy_static;
lazy_static! {
static ref CONFIG: config::Config =
Config::new().expect("to load config file");
}
#[get("/")]
async fn index(/*req: HttpRequest, */ hb: web::Data<Handlebars<'_>>) -> impl Responder {
/*
if let Some(val) = req.peer_addr() {
println!("Address {:?}", val.ip());
};
println!("ip: {:?}", req.connection_info().realip_remote_addr());
*/
let mut list:Vec<String> = Vec::new();
// add web services
let web_services = &CONFIG.web;
for web_service in web_services.into_iter() {
let mut service = "web:".to_string();
service.push_str(web_service);
list.push(service);
}
//add xmpp service
let xmpp_service = &CONFIG.xmpp;
let mut service = "xmpp:".to_string();
service.push_str(xmpp_service);
list.push(service);
// check services
let res = check(list.to_vec()).await;
let body = hb.render("index", &res).unwrap();
HttpResponse::Ok()
.content_type("text/html")
.body(body)
}
async fn check(list:Vec<String>) -> Map<String, Value>{
let mut obj = Map::new();
let mut vmap = Vec::new();
let mut map = Map::new();
#[derive(Debug)]
//#[derive(Debug, strum_macros::Display)]
enum Class {
Web,
Mail,
Xmpp,
Unknown
}
impl Class {
fn as_str(&self) -> &'static str {
match self {
Class::Web => "Web",
Class::Mail => "Mail",
Class::Xmpp => "Xmpp",
Class::Unknown => "Unknown",
}
}
}
//https://stackoverflow.com/questions/65040158/can-i-create-string-enum
for service in list.into_iter(){
let class = match &service {
s if s.starts_with("web") => Class::Web,
s if s.starts_with("xmpp") => Class::Xmpp,
s if s.starts_with("mail") => Class::Mail,
_ => Class::Unknown,
};
match class {
Class::Web => {
let n: Vec<&str> = service.split(":").collect();
let name = n[1];
let mut url = "https://".to_string();
url.push_str(&name);
//println!("service type: {:?}", class.as_str().to_string());
let web_service = Service::new(name.to_string(), ws(url.to_string()).await, class.as_str().to_string());
obj.insert("name".to_string(), Value::String(web_service.name));
obj.insert("status".to_string(), Value::String(web_service.status));
obj.insert("class".to_string(), Value::String(web_service.class));
vmap.push(obj.clone());
},
Class::Xmpp => {
let n: Vec<&str> = service.split(":").collect();
let name = n[1];
let xmpp_service = Service::new(name.to_string(), xs(name.to_string()).await, class.as_str().to_string());
obj.insert("name".to_string(), Value::String(xmpp_service.name));
obj.insert("status".to_string(), Value::String(xmpp_service.status));
obj.insert("class".to_string(), Value::String(xmpp_service.class));
vmap.push(obj.clone());
},
Class::Mail => println!("mail service"),
Class::Unknown => println!("Unknown service")
}
}
map.insert("data".to_string(), serde_json::json!(vmap).into());
map
}
//xmpp server check
async fn xs(server: String) -> StatusCode {
dotenv::dotenv().ok();
let xmpp_user = std::env::var("XMPP_USER").unwrap_or("user".to_string());
let jid = format!("{}@{}",xmpp_user,server);
let password = std::env::var("XMPP_PASS").unwrap_or("secret".to_string());
// Xmpp Client instance
let client = XmppClient::new(&jid, password.to_owned()).await;
match client {
Ok(_) => {
//println!("Client connected!");
client.expect("REASON").end().await.unwrap();
StatusCode::OK
},
Err(..) => {
StatusCode::INTERNAL_SERVER_ERROR
}
}
}
// web service check
async fn ws(url: String) -> StatusCode {
let client = Client::default();
let res = client
.get(url)
.send()
.await;
//res.unwrap().status()
match res {
Ok(status) => status.status(),
Err(..) => StatusCode::INTERNAL_SERVER_ERROR,
}
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
dotenv::dotenv().ok();
let port = std::env::var("PORT").unwrap_or("8080".to_string());
let address = format!("127.0.0.1:{}", port);
//println!("web: {:?}", CONFIG.web);
//println!("web: {:?}", CONFIG.web[0]);
// Allow bursts with up to five requests per IP address
// and replenishes one element every two seconds
let governor_conf = GovernorConfigBuilder::default()
.per_second(4)
.burst_size(2)
.finish()
.unwrap();
handlebars_helper!(compare: |a: String, b: String | a == b);
let mut handlebars = Handlebars::new();
handlebars.register_helper("compare", Box::new(compare));
handlebars
.register_template_file("index", "./static/index.html")
.unwrap();
let handlebars_ref = web::Data::new(handlebars);
HttpServer::new(move || {
App::new()
// Enable Governor middleware
.wrap(Governor::new(&governor_conf))
// Route hello world service
//.route("/", web::get().to(index))
.app_data(handlebars_ref.clone())
.service(Files::new("/static", "static").show_files_listing())
.service(index)
})
.bind(&address)?
.run()
.await
}
#[derive(Clone)]
//#[warn(dead_code)]
#[allow(dead_code)]
//#[allow(unused_variables)]
struct Foo;
// will return 500 error and 'Extract error' as content
impl KeyExtractor for Foo {
type Key = ();
type KeyExtractionError = SimpleKeyExtractionError<&'static str>;
fn extract(&self, _req: &ServiceRequest) -> Result<Self::Key, Self::KeyExtractionError> {
Err(SimpleKeyExtractionError::new("Extract error"))
}
fn exceed_rate_limit_response(
&self,
negative: &NotUntil<QuantaInstant>,
mut response: HttpResponseBuilder,
) -> HttpResponse {
let wait_time = negative
.wait_time_from(DefaultClock::default().now())
.as_secs();
response
.content_type(ContentType::plaintext())
.body(format!("Too many requests, retry in {}s", wait_time))
}
}