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>) -> 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 = 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) -> Map{ 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 { Err(SimpleKeyExtractionError::new("Extract error")) } fn exceed_rate_limit_response( &self, negative: &NotUntil, 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)) } }