use std::boxed::Box;
use std::collections::HashMap;
use std::net::{Shutdown, TcpStream};
use std::path::PathBuf;
use std::time::Instant;
use clap::{Command, Arg, crate_authors, crate_version, crate_description};
use rayon::prelude::*;
use reqwest::StatusCode;
use reqwest::blocking::{Client, Response};
use serde::Deserialize;
use serde_json::Value;
pub fn arg_handler() -> Option<PathBuf> {
let matches = Command::new("connchk")
.version(crate_version!())
.author(crate_authors!())
.about(crate_description!())
.arg(Arg::new("config")
.help("Path to the configuration file to use")
.index(1)
.required(true))
.get_matches();
matches.get_one::<String>("config").map(PathBuf::from)
}
#[derive(Deserialize, Debug, Clone)]
pub struct HttpOptions {
pub params: Option<HashMap<String,String>>,
pub json: Option<Value>,
pub ok: u16,
}
#[derive(Deserialize, Debug)]
pub struct Resource {
pub desc: String,
pub addr: String,
pub custom: Option<HttpOptions>,
pub kind: ResType,
pub res: Option<String>,
}
impl Resource {
pub fn check(&self) -> Result<(), Box<dyn std::error::Error>> {
match self.kind {
ResType::Tcp => {
self.check_tcp()?;
},
ResType::Http => {
if let Some(opts) = &self.custom {
self.check_http_custom(opts)?;
} else {
self.check_http_basic()?;
}
}
}
Ok(())
}
fn check_http_basic(&self) -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
let resp = client.get(&self.addr).send()?;
if resp.status() == StatusCode::OK {
Ok(())
} else {
let msg = format!("\n\tStatus: {}\n\tDetails: {}", resp.status().as_str(), resp.text()?);
Err(From::from(msg))
}
}
fn check_http_custom(&self, options: &HttpOptions) -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
let resp: Response;
if let Some(params) = &options.params {
resp = client.post(&self.addr)
.form(params)
.send()?;
self.custom_http_resp(options, resp)?
} else if let Some(json) = &options.json {
resp = client.post(&self.addr)
.json(json)
.send()?;
self.custom_http_resp(options, resp)?
};
Ok(())
}
fn custom_http_resp(&self, options: &HttpOptions, resp: Response) -> Result<(), Box<dyn std::error::Error>> {
let resp_code = resp.status().as_u16();
if resp_code == options.ok {
Ok(())
} else {
let msg = format!("\n\tStatus: {}\n\tDetails: {}", resp.status().as_str(), resp.text()?);
Err(From::from(msg))
}
}
fn check_tcp(&self) -> Result<(), Box<dyn std::error::Error>> {
let stream = TcpStream::connect(&self.addr)?;
stream.shutdown(Shutdown::Both)?;
Ok(())
}
}
#[derive(Deserialize, Debug)]
pub enum ResType {
Http,
Tcp,
}
#[derive(Deserialize, Debug)]
pub struct NetworkResources {
pub target: Vec<Resource>,
}
impl NetworkResources {
pub fn check_resources(&mut self) {
self.target.par_iter_mut()
.for_each(|el| {
let now = Instant::now();
match el.check() {
Ok(_) => {
let dur = now.elapsed().as_millis();
let res = format!("Successfully connected to {} in {}ms", el.desc, dur);
el.res = Some(res);
},
Err(e) => {
let res = format!("Failed to connect to {} with: {}", el.desc, e);
el.res = Some(res);
}
}
});
for target in self.target.iter() {
if let Some(result) = &target.res {
println!("{}", result)
}
}
}
}